These attempts at better GPU abstraction layers (starting from the Sebastian Aaltonen article and continuing with the Loon GPU API in the linked article or the Odin-based no_gfx) all have the issue that they're building on top of the current jank APIs we have.
Basically, they're all still concerned with the realities of current day GPU hardware, which in turn means they too, much like DirectX and Vulkan, will eventually become "bad" abstractions.
IMO, if there is any mention of a "vertex" or "fragment" shader, it's still not there yet. End goal should be massively parallel CPU, no graphics-specific functionality beyond compiler intrinsics for whatever can't be done efficiently in software. Something like CUDA is the target to aim for.
By far the best feature (IMO) is that it has no trouble talking to the most important Windows COM libraries (like DirectX) and the Objective-C runtime (for Metal), and this comes as "batteries included", not some random third party library you have to download.
So for any sort of visual engine development (game engines being the most obvious, but also applications like blender or the JangaFx suite which is the main user of Odin), it is great. What you need is just there ready to go.
The language design itself is very much oriented around appealing to people who do this sort of work.
Other than there isn't much to the language really. It lacks a "big idea" feature like Rust's lifetimes or Zig's comptime. The closest thing to a "big idea" is the rejection of package managers but that's not really part of the language.
It's pleasant to use and compiles fast. Hard to complain.
Clickbait video title, the first major release is going to be 2027 (date based versioning) (j/k).
Odin is a pretty neat language, I should play more with it. There's nothing outright wrong with it I can think of. Some things I would have done slightly different but they're all nitpicks (mainly having the context be a thread local so #contextless wouldn't be necessary).
The only alternative I'm aware of with a similar level of optimization is GCC's backend, which is just as slow.
Both should be much faster at compiling debug builds than they are though. There's an LLVM fork (TPDE-LLVM) that supports a limited set of backend targets but compiles way faster (order of magnitude) for O0, but for whatever reason they haven't managed to merge it with the mainline LLVM. Even with that there's still plenty of overhead from all the horrible C++ OOP-brained abstractions LLVM uses.
I get why you think this but you are incorrect. Why? Because managing a team of people is very very different from managing outsourced contractors, and LLMs are much more like the latter than the former.
A lease should have a clear timetable. How long am I leasing this thing for? 1 year? 3 years?
I'm fine with companies leasing software. I don't like it, and I much prefer buying, but that's fine. That is what software subscriptions are, the terms and conditions are clear.
"Buying" something where access can be revoked at any time, for any reason, needs to become illegal.
There are various ways to solve the problem (if you consider it a problem, I personally don't, there's no need for things to be this extremely decoupled, but for the sake of argument):
1) Allow objects to send messages to an implicit "parent" object, which supports "doesNotUnderstand" ala Smalltalk and that by default also forwards to the implicit parent. That means you can implement "log(...)" at the right point in the abstraction without manually passing a logger around.
2) Use algebraic effects, no need to handle this with OOP. A function requests the "log" effect and a handler defined in a higher level scope takes care of implementing log.
3) Have a language with an implicit context parameter that allows putting arbitrary things in it (closest would be Odin but Odin has a fixed set of operations in the context). Because it is implicit in the calling convention methods can be oblivious to it.
4) You separate the concerns: logging is the act of recording some information. Where that information is ultimately sent or stored is a separate concern. This is the more functional view of the problem. If you follow a "model-view-update" approach like the so called "Elm Architecture", logging is just another task produced by the view function, it doesn't need any special handling.
It's not that this is a "problem" of OOP, it is that OOP doesn't give you any tools for tackling this problem. That is why dependency injection frameworks exist. Some other paradigms and non-OOP architectures don't have any trouble with it. You can also replicate those architectures in an OOP language, but that's still "contorting the paradigm" rather than it helping or guiding you towards a clean solution.
Personally I think a global singleton logger is perfectly reasonable, it is other people (the ones using DI frameworks) that think otherwise ;)
No it is not a fault of OOP, it is just that OOP does not really provide the tools to help either. It is the fault of people with the wrong expectations or of those trying to sell snake oil.
OOP allows to create decoupled systems but it doesn't help in creating them. It is not an inherent property of the paradigm, but many people sold it as such.
"Use OOP and your application is now magically more modular! Buy my latest book to find out how!"
Since you mentioned you are a junior I guess this sort of nonsense is from before your time. No worries, that just means you won't have stupid dogma shoved down your throat. Use whatever paradigm if it helps you and avoid whatever gets in the way.
I think dogma is far worse than "tight coupling" or introducing mutation or even inheritance when it is the simplest and most direct solution to a problem. People get too hung up on the "ideal" way to do things.
I can criticize a paradigm while also using it anywhere and everywhere it benefits me ;)
It's not really true that components in physical engineering domains have no memory. The behavior of many physical components can only be described with access to a recent history. Other components, specially controllers, are typically state machines (which obviously have state).
But it is true that there will always be parts of a software system that are highly coupled. I'm not even sure if that's even a problem, unless the coupling is also highly tangled (meaning the connections are coming from too many places).
But if you want to decrease coupling between parts of a system, OOP by itself is not particularly good at it. Even something like an Entity Component System is usually less coupled than most OOP codebases because while each System is heavily coupled to the components they do work on, the Systems are usually fully independent from one another and easily replaceable.
Languages with refinement types (or contracts) like Dafny and Liquid Haskell can typically handle numerical predicates directly. Some can even handle string predicates directly, including regular expressions. They also allow you to write complex predicates as separate functions, albeit with limited expressiveness.
But you hit performance and/or outright computational limits (halting problem) rather quickly.
To be clear I'm not arguing against objects here, I don't disagree with anything you wrote. I'm only arguing against the somewhat commonly held idea that OOP helps with loose coupling.
Interfaces (a feature of OOP) can be used to decrease coupling, but the paradigm itself doesn't really provide any assistance in regards to coupling.
The default is for objects to directly instantiate other concrete objects and to be able to directly communicate with any object they at any point come in contact with. That is strong coupling, by default.
You have to do extra work to get looser coupling. Smalltalk style OO is much better (regarding coupling) because you don't have explicit interfaces, you can always replace an object with another. You can also query all live objects of a certain type and replace all of them with a proxy if you so wish. Way better.
But you can go even further in regards to loosening coupling. You can have objects communicate through a tuplespace (Linda), you can have objects receive and send messages only through ports, you can have objects send a message to their implicit parent who is then responsible for redirecting it.
Mainstream OOP is a very poor paradigm full of issues that gets mogged by everything.
Composition-over-inheritance is an admission of defeat for a paradigm that lacks native delegation support (good on Kotlin for realising this).
It even gets mogged by languages like Haskell that introduced the more loosely coupled idea of typeclasses, which were inherited by Rust (traits), Swift (protocols), Go (interfaces, not to be confused with declaration-time interfaces as in Java).
We dug a suboptimal hole and stuck our head in there for 50 years.
It's crazy to me that Linda came out 40 years ago. I think our field is completely blind to what was achieved in the past.
Smalltalk is to me the most obvious case where people somehow don't realize we had a fully live environment, where the entire IDE (or really the whole operating system) could have open heart surgery done on it while it was running, in the late 1970s!
And that's a language that is at least somewhat in the public consciousness, languages like Linda? Nobody knows them. Synchronous languages like Lustre and Esterel? Some engineers know them (and pay good money for them), but not software engineers.
Yes, for example if you are doing any sort of mechatronic systems engineering (cars, planes, rockets, etc.), you're typically using tools like Simcenter Amesim or Modelica or Simulink/Stateflow to digitally prototype the system.
These tools are all oriented around blocks with ports. They receive data on their input ports and emit data on their output ports. They do not know where that data is coming from nor where it is going. They cannot instantiate other blocks.
To create a new block you either create a primitive one (described directly in terms of differential equations, since these are physical simulations, or as actual code, be it C or MATLAB or whatever else), or you connect blocks together to form a larger block.
This creates a strict hierarchy, connections between blocks can only happen at the same level of abstraction. You can just rip out a component and replace it with another with the same ports, or with multiple components each filling in part of the job, or you can plop another component in the middle that's only used to process the data inbound and redirect it somewhere else.
This makes coupling extremely loose by design. The paradigm enforces it.
Dependency Injection frameworks are a workaround for limitations of the paradigm, much like Design Patterns also are. They're necessary (by a very stretched definition of necessary) because people want to do certain things and the paradigm is fighting them.
Some part of the code wants to do logging. The paradigm-native solution is to just instantiate a logger object directly, but that's not really what you want to happen, because there are application-level concerns as to where those logging messages should go.
So instead of a direct new, you create a Logger interface (extra work), then use the Factory pattern (extra work) to hide the concrete logger objects that get instantiated. Ok, but now you are still creating multiple independent loggers, which will trample on each other.
So you employ the Singleton pattern (extra work), but now you can't use different loggers for different parts of the codebase.
Ok, let's instead have the application instantiate the proper loggers, and then thread them through method arguments as needed (extra work).
But now passing all those loggers around is super annoying and extremely noisy, so you create a framework that can inject them where they are needed.
Great, now you have a whole framework just to pass some logger objects around. The framework is not the problem per se, it exists for a reason, the problem is that someone felt the need for it. And it is a massive pile of complexity that is NOT worth it.
The Alan Kay variant of OOP is superior in this regard, and I'd argue the Erlang variant even more so (Actors are just asynchronous objects).
But both of them are still suboptimal because they allow objects to instantiate one another and to directly communicate with whomever they come in contact with. That creates strong coupling and you have to "fight the paradigm" to avoid it.
Other engineering domains have this figured out: components can only send and receive data through ports, they are never aware of their siblings. Only a component at a higher level of abstraction can decide how the ports of lower level components connect to one another. That completely eliminates coupling.
(You can of course replicate this type of architecture with OOP, but you can also replicate it with any turing complete paradigm, that's neither here nor there)
I agree "high cohesion, loose coupling" is a good architectural property to strive for, but OOP is terrible at it and there's no reason to use it for this.
Trying to achieve "high cohesion, loose coupling" in OOP has led to the creation of (supposedly) best practice recommendations like the SOLID principles, and the development of monstrosities like dependency injection frameworks.
If OOP was actually good at "high cohesion, loose coupling", you wouldn't need them.
Basically, they're all still concerned with the realities of current day GPU hardware, which in turn means they too, much like DirectX and Vulkan, will eventually become "bad" abstractions.
IMO, if there is any mention of a "vertex" or "fragment" shader, it's still not there yet. End goal should be massively parallel CPU, no graphics-specific functionality beyond compiler intrinsics for whatever can't be done efficiently in software. Something like CUDA is the target to aim for.