Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the performance of the GDScript VM #6031

Open
reduz opened this issue Jan 1, 2023 · 219 comments
Open

Improve the performance of the GDScript VM #6031

reduz opened this issue Jan 1, 2023 · 219 comments

Comments

@reduz
Copy link
Member

reduz commented Jan 1, 2023

Describe the project you are working on

Godot

Describe the problem or limitation you are having in your project

Many users have complained for a long time about the performance in GDScript.

For the GDScript 2.0 rewrite, a lot of work was put into making sure the VM has all the information required to generate optimized code, including type information, strict type checking, better addressing modes and the ability to access every operator, utility function, etc. as a C function pointer. This can be evidenced by the new codegen class used to emit code.

Unfortunately, just the changes to the parser and compiler ended up being very significant so almost no optimization work could take place for the VM.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The idea is to use the the new type information as well as all the C function pointers to engine to generate direct executable code and bypass the VM, allowing for much greater performance when typed code is used.

This is challenging to do in a practical way, however. Because of it, this is more of an open discussion on evaluating different alternatives.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

There are a few general approaches that could be taken to optimize GDScript:

  • JIT
  • real-time AOT
  • offline compilation.

The problem with JIT and real-time AOT is that there are many platforms that do not support real-time code generation, such as iOS, WebAssembly or consoles. Because of this, if we followed this approach, Godot would not run well on those.

As a result, the ideal way to compile GDScript code to binary would be to compile the whole script to a shared object (DLL on Windows, .so on Linux, .dylib on MacOS, etc) that can be bundled together with the exported game.

Fortunately, GDExtension provides pretty much everything the compiled script needs:

  • A standard C ABI that allows accessing all the engine API and C function pointers.
  • The means to bundle the shared object with the game on export.

The question that remains is how to build cross-platform code from the editor. There are a few alternatives to doing this:

  • Require the user to install an external compiler (like GCC or LLVM) and basically generate the C code for the GDExtension, which the GDScript VM will optionally use to run instead of the interpreted code.
  • Integrate some compiler infraestructure like LLVM or QBE to generate this.
External compiler

This is one of the easiest solutions, but it can be quite bothersome for the user to have to do this usability wise. It would make distributing Godot harder if the intention is to make sure code just runs fast. For some platforms like WebAssembly, I am not sure if there is any other way though.

There are tiny and embedded compilers that may be of use, but none really seems fit our needs:

  • TCC: tiny, actively developed and embeddable C compiler that seems to meet all our needs. But license is LGPL.
  • ChibiCC: Seems abandoned.
LLVM / QBE

Another alternative would be to integrate LLVM or QBE into Godot and generate the binary on the fly.
LLVM looks like a lot of work though, since supposedly you have to implement the C ABI code yourself and its codebase is huge. QBE sounds like a much better project for this (it already has full C ABI support) but, while active, its developed by a smaller team and they don't use GitHub.

Lower level binary geneator

There are lower level libraries like SLJIT that do cross-platform codegen. This could work as an AOT compiler for platforms that can do this, and the code generated could be dumped to an shared object in advance on export for the platforms that do not support it (this would need some work).

The downside of this is that these libraries perform no optimization, so GDScript would need its own SSA form optimizer. This may not be terrible, but its some work.

So, what do you think? how would you tackle this problem in a way where the usability and maintainability balance is good in our favor?

If this enhancement will not be used often, can it be worked around with a few lines of script?

N/A

Is there a reason why this should be core and not an add-on in the asset library?

N/A

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

If we can't find a single solution that works, it may be possible we can just do an AOT compiler for platforms that support codegen, and have alternative write to C/GDExtension for platforms that do not.

@vpellen
Copy link

vpellen commented Jan 1, 2023

Disclaimer: My knowledge on compiler infrastructure and build systems is limited.

For me personally, the concern is less that GDScript is slow, and more that the alternatives are miserable to work with. My language of choice for many years was C++, and so GDExtension looks nice enough, but having to download external tools and build systems and thread everything together gets in the way of what I want to do, which is write performant code to solve my problems. I don't feel like I gain any meaningful control from choosing a compiler infrastructure or refining the build system.

I also don't really care about editor integration as much as I care about ease of use. If I could write a single trivial .cpp file and drag and drop it over some random executable that did all the boilerplate garbage and spat out a DLL for me to plug into my project, I wouldn't give a damn about editor integration.

So perhaps a distinction needs to be drawn. Are we trying to:
A) Improve net performance for the average user, or
B) Reduce friction for advanced users trying to accomplish specific ends?

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@vpellen The goal is to improve performance for the average user, so definitely this is not for you.

@AThousandShips
Copy link
Member

I think I'd be in favor of having a good GDScript centric optimizer, working against the codegen format of GDScript, with the option of exporting in executable format via some method. It's a big undertaking but I feel it ensures that GDScript behaves well in all cases (including dynamically created scripts), and makes prototyping easier (not having to export each time to get the optimization but only missing the boost from executable format)

@jabcross
Copy link

jabcross commented Jan 1, 2023

If we go with LLVM, I want to recommend using MLIR. It's somewhat of a more powerful, more generic evolution of LLVM IR that could allow some other things in the future, like compiling GDScript to compute shaders directly.

There's a lot of research at the moment and it's pretty powerful. (It's used by TensorFlow, for instance).

There are already abstractions for most of the common programming language features like structured control flow, functions, etc. And it already has a lot of stuff implemented, like the propagation of debug symbols. So line-by-line debugging would just be a matter of plugging into LLDB.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@jabcross The problem with LLVM IMO is that its enormous and that you are on your own for a lot of stuff to generate IR code. Given GDScript demands a ton of C ABI interoperabilty, C should work better and be simpler than an IR, but of course things are more complicated from there. This is why QBE seems more interesting than LLVM in the fact that its designed around C ABI interop.

I also don't think GDScript has a ton of optimizer potential that LLVM can take advantage of, so another possibility is to do simpler optimization manually and then run on something like SLJIT.

@chamakits
Copy link

(Not a core developer, just a big fan and casual/average user of Godot, mostly through GDScript, dabbled with Mono)
Would this new solution mean to replace the current GDScript implementation, or to exist side by side permanently?

I'm asking because GDScript being a simple included VM is to me one of the biggest advantages of Godot for people to get started. Not requiring a compile step makes iterating on code changes very quick, and having everything included in the editor without any external install makes it the most pleasant Game Engine I've used. I think Godot's GDScript VM is what gives Godot such great ergonomics, at least for average users (like myself :D ).

If this is an alternative to the VM that lives side by side, then I'm a bit less concerned about the ergonomics being impacted. But I'd still be concerned if that increases the workload on the team. It seems it'd be effectively another whole back end (or set of back ends) that is officially supported, and may make future development much more complicated as it requires supporting many back ends. If the team has the bandwidth, then that concern is also addressed.

Do average users run into performance issues frequently enough with GDScript that it merits addressing with this large a shift in implementation strategy? Is there any usage pattern that can be identified that would be better addressed providing some other tooling (for example, nodes that abstract away the concept of resource pooling if a frequent performance concern is creating too many entities)?

@bruvzg
Copy link
Member

bruvzg commented Jan 1, 2023

TCC: tiny, actively developed and embeddable C compiler that seems to meet all our needs. But license is LGPL.

It's x86 and Linux + Windows only.

Cross-building with any external compiler will be a huge pain. Any version of clang should be able to generate code for any target, but linking the final shared library and any use of C/C++ standard libraries won't work without access to the platform SDKs for each target. But I guess the same will be true for the integrated LLVM / QBE as well.

@jabcross
Copy link

jabcross commented Jan 1, 2023

@reduz MLIR addresses a lot of these issues, but you're right that, at least for development of the engine itself, LLVM is quite big. But the compiler could dynamically link to the LLVM library once it's done, so the end users wouldn't be exposed to that.

The GDScript optimizer could be made as a separate module that most engine devs wouldn't have to compile.

MLIR provides a number of ways to deal with C ABI interop. (I had to learn them for my MsC project). There's also a C/C++ MLIR dialect being worked on that will greatly simplify connecting directly to C code at any level of abstraction, so we won't be limited to the ABI forever.

@vpellen
Copy link

vpellen commented Jan 1, 2023

@vpellen The goal is to improve performance for the average user, so definitely this is not for you.

Hey, I'll have you know I'm remarkably average!

Jokes aside, the reason I bring it up is because robust maintainable large-scale integrated performance improvements are probably going to be exceedingly difficult to implement. The average user is not going to be well versed in build systems and compiler infrastructure, which means if you want a good solution it'll have to be integrated with the existing editor in a pretty seamless way.

From what little knowledge I have on the subject, LLVM would probably be the most robust and future proof, but the overhead concerns are real, and I feel like Godot has a history of rejecting existing external codebases in favor of home-grown ones (Bullet is the most recent example of this I can think of). I have a sneaking suspicion that the larger codebases will be deemed excessive, while the smaller ones won't quite fit the needs, which will lead to the development of a custom solution that will take three years to develop and be miserable to maintain.

I'm hoping my concerns are unwarranted.

@Splizard
Copy link

Splizard commented Jan 1, 2023

Something not mentioned yet but I think is worth seriously considering here is using WASM itself as the GDScript intermediary format. There are plenty of fast and actively maintained FOSS WASM runtimes, (AOT, JIT and interpreters).
https://github.com/appcypher/awesome-wasm-runtimes

Wasmer (AOT & MIT), WAVM (JIT & BSD) and wasm3 (interpreter & MIT) stick out to me as being the most performant in their respective categories. They are all close to native (WAVM even claims to exceed native performance in some cases).

Using WASM for this would also enable a better extension experience. No more hassle with making sure that the correct binaries are built and included for all possible platforms ahead of time. Just drop in your one WASM file and use any language that supports a WASM target to write Godot extensions, the WASM runtime will take care of executing the extension efficiently on any of the export targets.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@Splizard WASM is overkill IMO and the memory isolation for something like this really gets in the way. It also does not solve the problems mentioned above (export to platforms that need native AOT).

@badlogic
Copy link

badlogic commented Jan 1, 2023

As someone who actually build a commercial AOT compiler from JVM bytecode to x86 and ARM machine code using LLVM, I think compiling to C is the best option for various reasons:

  • It's way simpler than learning and dealing with LLVM's ever changing APIs.
  • The emitted code is much more easily inspected and debugged.
  • It's easier for contributors to bug fix as the complexity and needed knowledge is a lot lower than contributing to an LLVM based machine code emitter.
  • You can target new architectures/platforms with ease.
  • You still get all the optimizations (mostly) you'd get when emitting machine code directly.

The one downside is that it's harder to emit debugging information, if it's a goal that the AOT compiled code should also be debuggable via the GDScript debugger. I don't think that's a requirement for a V1.

The other downside you mentioned is requiring a C compiler. I don't think this is a problem though. Godot could bundle or on-demand download Clang on all platforms. That's not really much more in download size than bundling the LLVM binaries. If needed, a customized Clang build could be maintained that strip away anything not needed.

@Zireael07
Copy link

Seconding @badlogic in that LLVM is not only big, but also changes A LOT.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@badlogic I think the AOT route via C is useful, but its still more hassle than what Godot users are accostumed to. IMO this is something that we should have to support anyway at some point (else Wasm, iOS or console optimization will not happen) but, as you say, for development this is a hassle.

To me C AOT (basically compiling GDScript to a C code that uses the GDExtension API) is something that is very simple to do (likely few thousands loc) and maintain.

But for development, my feeling is that we should have something that works out of the box, which is why I still think something like SLJIT makes more sense. The only thing you miss is the optimizer pass, but given the nature of GDScript I doubt this will be much a problem and the performance gain will still be very significant.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

For reference, either as LLVM, C or SLJIT, all that needs to be done is provide a VM back-end:
https://github.com/godotengine/godot/blob/master/modules/gdscript/gdscript_vm.cpp
That builds its data from the generator:
https://github.com/godotengine/godot/blob/master/modules/gdscript/gdscript_byte_codegen.h

Either spitting C or building an SLJIT execution buffer based on this is not a lot of work (order of thousands of lines of code). The reason is that most of those instructions, if you look closely, already call C functions to do most of the heavy loading (as an example, all built-in types, operators, constructors, and all the entire engine API are available via C function pointers). Eventually, significant optimization can be achieved by providing specialized versions of the instructions when the types are known (the codegen.h already lets you know this, so this work is already done). Even adding an SSA optimizer with most common optimizations would likely not be very hard.

In contrast, writing an WASM, LLVM or QBE back-end would probably be orders of magnitude more complex, because you need to replace all this with inline code in IR format. Of course, this has more optimization potential than the previous methods, but the amount of work required and the maintenance requirements are enormous.

@badlogic
Copy link

badlogic commented Jan 1, 2023

@reduz I don't think a C generator + compiler requirement is a hassle. Users already have to download export templates for all platforms. Providing the user a single click download inside the editor to fetch (a trimmed down) Clang seems to be pretty OK to me in terms of hassle.

As you said, you will need an AOT solution for iOS and WASM anyways, where SLJIT won't work. It's also questionable if an SLJIT generator can actually achieve any performance improvement compared to the current bytecode interpreter.

@Splizard
Copy link

Splizard commented Jan 1, 2023

@reduz

It also does not solve the problems mentioned above (export to platforms that need native AOT).

WASM runtimes totally support native AOT.

Wasmer 1.0 introduces “wasmer compile”, a new command for precompiling Wasm files. Our customers and developers use “wasmer compile --native” to precompile WebAssembly modules into native object files like .so, .dylib, and .dll.

(https://medium.com/wasmer/wasmer-1-0-3f86ca18c043)

the memory isolation for something like this really gets in the way

Reference types in WASM are a thing (also supported by wasmer), I'm not sure about the performance profile of say keeping all packed arrays and strings as reference types but it's certainly a solution.
Modding is certainly an area where WASM runtime isolation could be helpful.

As @vpellen indicates, I don't think you can beat the user experience of dropping a single WASM file into your project and being able to export this seamlessly to mobile devices, consoles, etc. Let the WASM runtimes focus on performance!

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@badlogic It is a hassle if this is a requirement to even use the engine. Godot is a tiny download that just works and a large amount of the Godot community largely appreciates this. The export templates are only downloaded at the time of exporting the game, but you don't need them to start working quickly.

I agree with you that this is likely the path we will have to take at some point, but it should only be an optional step for games that only really need this extra performance. The current bytecode interpreter still has a good amount of room for optimization (addressing needs to be further simplified and typed instructions need to be added), but as the intepreter is a gigantic jump table, it still has a big toll on the L1 caches and branch predictor, which should be significantly improved with something like SLJIT, and that works out of the box with little work.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@Splizard Its good to know you can handle AOT but, as I said, I still think the isolation sucks and creates a lot of glue friction that I would be happy to avoid. Additionally, you still need some sort of compiler so, in the end, the advantage provided is minimal vs the drawbacks.

@YuriSizov
Copy link
Contributor

YuriSizov commented Jan 1, 2023

For the reference, there is an existing proposal for a WASM runtime with a decent amount of support, but not much discussion: #3370

@xsellier
Copy link

xsellier commented Jan 1, 2023

I know it is an unpopular opinion, what about dropping GDScript and using Python instead?

I would like to clarify my point. As a matter of fact, writing a programming language is the work of a full-time team. I find it ambitious for Godot Engine to create a game engine with a whole new approach with a new programming language.

Perhaps Godot Engine should do with GDScript the same thing that happened with VisualScript. At the same time switching to another language with a syntax close to GDScript, such as python. It should close a lot of tickets directly related to GDScript and allow us to focus on the core of Godot Engine, i.e. being a game engine.

I'm aware that this is only my opinion, and that I'm not the person who maintains GDScript, nor the one who will be in charge of switching to Python, and even less the one who maintains Godot Engine in a broader sense.

@YuriSizov
Copy link
Contributor

@xsellier
Juan just recently recalled the creation of GDScript on Twitter, and mentioned why Python was not a good option. It is also covered in part in our official documentation.

We like GDScript because it allows us to tightly integrate into the engine, to remove a lot of overhead both conceptually and in terms of the interop. Maintenance of our own tool does come at a cost, but so does maintenance of a blackbox solution that we must embed into our project. Take our approach to physics for example, you see the same thing. We opted to abandon Bullet as the main backend because it is very hard to keep that bridge between the two parties sound. Same applies here.

Aye, it will close some bugs in our codebase. But it will also open new bugs which may not be within our reach to fix. So we'll have to bother other projects with our issues, projects which may not share our goals or don't see our problems as worthwhile. But our responsibilities before our own users won't go anywhere, so we'd have to either take the blame or ship some hacks and ad-hoc fixes, if we can. That's not great at all.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@xsellier Python is a terribe option. Letting aside that there is not a good VM that we can use and that all the nice integration to the editor would be lost, the VM is not designed to work standalone. Even if you make it work standalone, you lose access to the rest of the ecosystem, or you bloat massively your exports as a result.

Python is designed so applications are parts of its ecosystem and not the opposite. It definitely is nice to have Python support for some things (if you work on scientific or academic fields as an example) but it's not meant to be used for something like Godot.

@xsellier
Copy link

xsellier commented Jan 1, 2023

I get your point, Python might not be the best choice because of the size of its VM.

My point is still relevant, writing a programming language is a full-time job for a whole team of dev. Meaning, either hiring more devs to work on GDScript, or dropping it and take another already existing programming language.

I'm quite happy about what's GDScript is becoming, First class functions, static methods, new built-in types... but for the last 2 years, progress reports about GDScript are quite sparse ( https://duckduckgo.com/?sites=godotengine.org%2Farticle&q=GDScript&ia=web ). I mean I'd expect more update on GDScript since it is a programming language with quite some bugs ( https://github.com/godotengine/godot/issues?q=is%3Aopen+is%3Aissue+label%3Atopic%3Agdscript ). Some bugs are more critical than others and lasts for years ( godotengine/godot#7038 ). I'd like to state it is not a complain, because I use GDScript and I like the way it is, it is more like a realistic statement.

My point is more, creating a programming language is too much work for Godot Engine atm, it might be better to think about dropping it, to focus on what's imprtant for the engine. You can focus on Python if you want, I was talking about dropping GDScript in favor of something else.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@YuriSizov Its probably not a lot of work to have a Wasmer module that can load GDExtensions inside the runtime compiled for Webassembly. There is likely not a lot of merit either, but using it for running GDScript is definitely not a good solution. GDScript uses engine types for everything, so the isolation means the glue needs to either spend a lot of time converting the engine types to communicate with the engine, or it needs to keep types as external references and access to them is slower.

This is what happens in C# as an example, where you need to be conscious on using engine types or native types depending on what you need for the most part (accessing data or passing it), which is fine for this language as users are expected to be more experienced. But for something easy to use like GDScript that is meant to "just work", it will cause a lot of friction.

@YuriSizov
Copy link
Contributor

YuriSizov commented Jan 1, 2023

My point is more, creating a programming language is too much work for Godot Engine atm, it might better to think about dropping it, to focus on what's imprtant for the engine. You can focus on Python if you want, I was talking about dropping GDScript in favor of something else.

@xsellier
Besides a couple of links to address your specific proposal at the start the rest of my comment was not about Python, it was exactly about the cost of dropping a bespoke solution which is GDScript for something else. You keep implying that using an off-the-shelf language would somehow be free, or at least cheaper to maintain. I made a point that it's not the case.

@reduz
Copy link
Member Author

reduz commented Jan 1, 2023

@xsellier The problem with your logic is that, what I believe you fail to understand (likely due to lack of experience in the field) is that Integrating another programming language to Godot and keeping the fantastic "just works" usability GDScript is even more work than developing GDScript.

Writing glue code is difficult and there are a lot of corner cases:

  • How do you handle type conversions to engine conversions efficiently?
  • How do you handle threads?
  • How do you handle a class model that resembles and references the same as the engine?
  • How do you handle exporting and packing?
  • How do you handle reference assignment and garbage collector with engine types?
  • Etc.

I already integrated other languages such as Lua, Squirrel and Python to Godot. It was a ton of work and generally there is more code to do the glue than there is GDScript runtime code, and inefficiencies everywhere when getting data one way or the other (same reason why I think Wasm as script runtime is a terrible idea). This without even getting into all the nice editor integrations GDScript has to the engine, which would be a massive amount of work to do in another language (where in GDScript they are tiny).

Building software is not stacking lego bricks where you add something existing and it magically works. Getting the pieces to fit and keeping them together often requires as much effort as writing the pieces yourself.

@stucotso
Copy link

For comparison, doing the same type of benchmark with another simpler renderer (Raylib) with Shedskin-generated-C++ from Python gives about 200k entities before dropping framerate.

Given we're getting 52k in Godot vs 200k in Raylib, (Raylib has much simpler renderer and was using batching, I didn't optimize draw calls for Godot) but it can give an idea of how many more entities are possible from C++ on the same hardware.

But assuming we can reach similar speed this would mean a 20x speedup over GDScript.

image

Animated gif here of the Raylib bunnymark with ShedSkin AOT build from Python.

@stucotso
Copy link

Bunnymark with average distance computations:

  • C++ can have about 1500 sprites on screen before framerate drops below 60
  • GDScript can do about 180 sprites before it dropped below 60
  • This amounts to factor of about 8.3x

I just realized I missed a massive mistake on this benchmark.

The average-distance algorithm is brute force, so O(n^2).

This would mean:

  • GDScript is processing 180 entities, that means 180*180=32,400 distance computations per tick
  • C++ is processing 1500 entities, that means 1500*1500 =2,250,00 distance computations per tick
  • This gives an overall speedup of about 70x on PURE computational stuff, with minimal Godot API mixed in (distance function), if doing a transpile AOT from GDScript to C++

@SlugFiller
Copy link

Can't help but notice the GDScript is largely untyped. Typing is known to make anywhere between x2~x20 difference. Plus, the original description made was about transpiling a typed subset of GDScript.

There are also other places where I feel like the code is not equivalent. Like manually inlining sprite.tick. This would have been fair if done on the GDScript side, because a C++ compiler is likely to inline the function anyway. But doing it on the C++ side makes it an apples to oranges comparison, because the GDScript compiler will absolutely not inline it, especially on untyped code.

There are other aspect, like the fact that on the C++ side sprite is not a GDCLASS and does not extend Object, making the code not equivalent (in GDScript, sprite can be used from other files, and can be stored in a node's metadata, while the C++ equivalent cannot). If I nitpick, the list would be long.

The point I'm making is that this isn't a benchmark about the potential speed of GDScript vs transpiled C++. It's comparing GDScript to hand-optimized C++. Including optimizations a compiler can't automatically do, even if it was smart enough, because they can change program semantics. A proper comparison would be with a naive port, that mimics what the GDScript VM does, minus the part where it reads the code from memory and does a switch.

@stucotso
Copy link

A proper comparison would be with a naive port, that mimics what the GDScript VM does

Agreed - I'll try to also get things up into Github (and cleaned up significantly) so folks can jump in and tweak easily. I'm also really curious to see how other folks' hardware/compiler/OS configurations would impact numbers.

@AndrewAPrice
Copy link

Can't help but notice the GDScript is largely untyped. Typing is known to make anywhere between x2~x20 difference. Plus, the original description made was about transpiling a typed subset of GDScript.

We could have warnings (and some users might even want errors) when the transpiler can't determine the type and has to fall-back to untyped Variants.

(We're not talking about JIT here, which is way more complicated than transpiling, but there have been solutions to improve the performing of JIT'ed dynamically typed code such as basic block versioning: https://pointersgonewild.com/2015/09/24/basic-block-versioning-my-best-result-yet/)

@AndrewAPrice
Copy link

AndrewAPrice commented Jan 23, 2024

Cross-building with any external compiler will be a huge pain. Any version of clang should be able to generate code for any target, but linking the final shared library and any use of C/C++ standard libraries won't work without access to the platform SDKs for each target. But I guess the same will be true for the integrated LLVM / QBE as well.

You don't need platform SDKs. The transpiled code would only call back into Godot's functions, and so Godot would provide a Godot.h that exposes only Godot's data types and methods.

I do like Clang and how it can generate code for any target out of the box. As @badlogic said, we could offer a one click button to download a trimmed down Clang, or the user could provide their own path to clang.

@linkerlin
Copy link

Has anyone noticed a project that integrates WebAssembly and Godot: https://github.com/ashtonmeuser/godot-wasm? I tried it and it's really good. It's quite convenient to use Golang to generate wasm modules and then call them.

@jasonwinterpixel
Copy link

There are 3 options for this

  1. Improve the GDScript VM without a large redesign, just squeeze more water from the stone
  2. Transpile GDScript to C++
  3. Compile GDScript directly

Option 1 is simplest, and there's probably a lot that can be done to improve the GDScript VM. Just speculating here, but I imagine that better control of memory allocation would likely improve GDScript's performance. This option should basically always be done at some level as it will improve the raw GDScript interpretor's performance.

Option 2 would not work well for most users. Most users would not be able to then compile that c++. But for advanced users and professional teams, we can setup our own pipeline to compile GDScript to c++, as long as that pipeline exists. This would allow us to use the c++ ecosystem of profilers and debuggers to analyze our products more easily than using godot's built in tools for GDScript. It's probably not that hard to transpile a subset of GDScript to c++, and there's probably some stuff that is harder to put in c++. Perhaps that stuff can just be ignored. I think this would work as an external tool to Godot, perhaps not even 'official'.

Option 3 is far more intense and would require a high level programmer to invest a serious amount of time. Note that QBE is an alternative to LLVM that is far smaller: https://c9x.me/compile/ Unfortunately it doesnt have a .wasm target. If QBE had a wasm target, it would be a great possibility to leverage for deep integration with the godot engine to produce highly optimized exports.

Note that for our game studio, we manually rewrite a lot of prototypical GDScript into c++, but we at least have a powerful set of macros to make writing that C++ less verbose.

@Zireael07
Copy link

I know someone did option 2 in a bit convoluted way (GDScript to Python to C++ via IIRC Nuitka) 🚀

@filipworksdev
Copy link

filipworksdev commented Mar 8, 2024

It's probably not that hard to transpile a subset of GDScript to c++, and there's probably some stuff that is harder to put in c++. Perhaps that stuff can just be ignored.

Static GDScript can very likely be easily transpiled where as dynamic generic var might be harder. This is also incidentally my favorite option since GDScript + C++ create a more powerful combination than C# alone.

@IntangibleMatter
Copy link

IntangibleMatter commented Mar 8, 2024

Static GDScript can very likely be easily transpiled where as dynamic generic var might be harder. This is also incidentally my favorite option since GDScript + C++ create a more powerful combination than C# alone.

To be fair, those could just be statically typed as Variant, as they tend to be in Godot's source. Not the most efficient solution, but one that should work fine.

@Sciumo
Copy link

Sciumo commented Mar 13, 2024

Someone has Zig bindings.
Which has a simplified build environment, portable, supports wasm, etc.
https://github.com/godot-zig/godot-zig

@Lcbx
Copy link

Lcbx commented Mar 14, 2024

There are 3 options for this
1. Improve the GDScript VM without a large redesign, just squeeze more water from the stone
2. Transpile GDScript to C++
3. Compile GDScript directly

about option 2, I made a gdscript transpiler to both C# and c++
Personally I use gdscript for UI, C# for game logic (& access to nuget), and a little c++ for very specific components and tools.

The way I see it, gdscript is more about the convenience of having a godot-specific language integrated into the engine ; this means that simple, non performance-critical logic can be written in editor and modified on the fly.

I understand the appeal of being able to develop a game form A to Z in the godot ecosystem ;
but maintaining a game engine and a scripting language seems like a big burden, if you also want to make said the language performant (with all the complexity of such a task), you're going to have a hard time.

If users need performance without touching c++, there's C# and the plethora of GDextension bindings (lua, zig, swift, nim, etc).
Actually, if we want a scripting language with JIT, lua is probably our best bet (let's call this option 4).

@CrayolaEater
Copy link

CrayolaEater commented Mar 15, 2024

I feel like some of the godot users don't understand the power of GDExtension. If you want to run fast (C++ fast) just use that. Why GDScript needs to compete with other languages in terms of speed?

This is my approach of using Godot:

  • Use GDExtension for API (Backend)
  • Extend the functionalities in GDScript (Frontend)

I think that simply just improving the VM is the best and "easiest" way of going forward with GDScript.
Some of the users complain about the game shipping with the interpreter and parser for the GDScript, but how big is that exactly? I don't think there is a significance amount. In future maybe there could be an intermediary representation for the language in order to make the game ship with a simpler parser and smaller interpreter?
Right now there are tons of optimization that can be made for the VM so there is room for improvements.

@vpellen
Copy link

vpellen commented Mar 15, 2024

GDExtension is great. Wrangling an external build system is not so great, especially for the average user (which, note, very few of us are - we're exceptional by simple fact that we're participating in a github discussion).

@Lcbx
Copy link

Lcbx commented Mar 15, 2024

GDExtension is great. Wrangling an external build system is not so great, especially for the average user

that's why I mentionned lua as an alternative ; it is a mature scripting language that comes with both performance and a JIT.

The work of decoupling the scripting interface from gdscript was done in godot 4 (gdscript 2.0), adding official support for lua is not going to be that hard, especially since c++ scripting is what the lua vm was designed for.
While it's different from gdscript syntactically, if Roblox kids can use it, the average godot user can.

Godot is not Epic or Unity. I don't think you'll find someone willing to do the hundred-ish man-hours necessary to fit JIT/AOT into gdscript for free ; we're talking several months of work here. And asking one of the core contributors is taking manpower away from what is already a very small team by industry standard.

I actually prefer gdscript over lua when it comes to writing code ; I wouldn't have taken 2 months of my life building a transpiler if I didn't. But if you want to do heavier computation in your scripting language, lua or a similar existing language (julia, wren, etc) is what we should be looking at.

It's the same with godot physics ; the default version is good enough for most uses, but if you need something more stable or performant, you use godot-jolt. Reinventing the wheel comes with costs.

@stucotso
Copy link

I wonder if anyone has seen any kind of survey or data on what the Godot userbase looks like, or maybe even anything official on what the Godot dev team likes to 'optimize' for in terms of featureset? I think if one considers different target audiences (or users of Godot), the 'right' approach to solve this bug changes - one of the reasons for the wide variety of ideas on this thread is really mostly due to folks assuming a different audience is the target.

For example, based on target audience I could see a breakdown for how to enhance GDScript performance to be like:

  1. Beginner audience: This group won't push GDScript hard enough to justify any VM improvement, and if they hit a limit it's likely due to other issues / poorly written code.
    • Solution: Improve GDScript docs
  2. For-Profit Solo Game Dev: This group may be able to hit limits in GDScript, and optimizing existing VM probably might make material improvements to their game dev experience. They're likely not going to have time for maintaining seperate C++ or C# and would prefer simplicity of GDScript
    • Solution: Boot up a profiler, find the slow bits in GDScript VM, optimize them
  3. Small game team / company: Teams like this would normally have bandwidth to use/maintain a C++ compiler toolchain. Likely would be limited by GDScript's performance but are capable of using C++ or C# to optimize parts of their game.
    • Solution: I think this audience would love to have ability to start with GDScript, and have some tool to transpile to faster language (like C++). Just optimizing the interpreter might not give the speed they need.
  4. Bigger game corp / company: Likely to consider Unreal or Unity first, but if they choose Godot it's probably for licensing or for its simplicity. They're likely to be able to hit GDScript's limits and can afford to write C++
    • Solution: Same as option 3 - but they could even afford a clunkier/worse user experience as they can afford to hire the engineers to deal with it

(Those are all probably wildy wrong assumptions - but with some data we could get a better idea. I just wanted to toss out the idea that we should consider who to optimize for...)

Say if the Godot teams wants to see more commercial games out with Godot, I think they'd cater to audiences 3 & 4 (who make the majority of games out there) but if they'd like to simply boost adoption / # of users, catering to audiences 1 & 2 makes more sense.

@Foxchandaisuki
Copy link

There are 3 options for this

  1. Improve the GDScript VM without a large redesign, just squeeze more water from the stone
  2. Transpile GDScript to C++
  3. Compile GDScript directly

Option 1 is simplest, and there's probably a lot that can be done to improve the GDScript VM. Just speculating here, but I imagine that better control of memory allocation would likely improve GDScript's performance. This option should basically always be done at some level as it will improve the raw GDScript interpretor's performance.

Option 2 would not work well for most users. Most users would not be able to then compile that c++. But for advanced users and professional teams, we can setup our own pipeline to compile GDScript to c++, as long as that pipeline exists. This would allow us to use the c++ ecosystem of profilers and debuggers to analyze our products more easily than using godot's built in tools for GDScript. It's probably not that hard to transpile a subset of GDScript to c++, and there's probably some stuff that is harder to put in c++. Perhaps that stuff can just be ignored. I think this would work as an external tool to Godot, perhaps not even 'official'.

Option 3 is far more intense and would require a high level programmer to invest a serious amount of time. Note that QBE is an alternative to LLVM that is far smaller: https://c9x.me/compile/ Unfortunately it doesnt have a .wasm target. If QBE had a wasm target, it would be a great possibility to leverage for deep integration with the godot engine to produce highly optimized exports.

Note that for our game studio, we manually rewrite a lot of prototypical GDScript into c++, but we at least have a powerful set of macros to make writing that C++ less verbose.

I think option 3 is the best.

@Foxchandaisuki
Copy link

If we can't find a single solution that works, it may be possible we can just do an AOT compiler for platforms that support codegen, and have alternative write to C/GDExtension for platforms that do not.

@reduz Why don't we just make AOT compiler for GDScript like the Dart language developed by Google has?

The target platform doesn't need to support codegen in this case.

real-time AOT

Why do we have to AOT compile the game when the game is executed, not when the game is built?

As a result, the ideal way to compile GDScript code to binary would be to compile the whole script to a shared object (DLL on Windows, .so on Linux, .dylib on MacOS, etc) that can be bundled together with the exported game.

And why compiling to DLL is the best option instead of making standalone exe file?

@atirut-w
Copy link

atirut-w commented Apr 5, 2024

And why compiling to DLL is the best option instead of making standalone exe file?

Because segmentation and housekeeping a bazillion of executable files. No game engine does that.

@Foxchandaisuki
Copy link

And why compiling to DLL is the best option instead of making standalone exe file?

Because segmentation and housekeeping a bazillion of executable files. No game engine does that.

@atirut-w Do you have any idea about other questions?

@atirut-w
Copy link

atirut-w commented Apr 5, 2024

The other questions do have good points. No objections there 👍

@Qws

This comment was marked as off-topic.

@Qws
Copy link

Qws commented Apr 14, 2024

I feel like some of the godot users don't understand the power of GDExtension. If you want to run fast (C++ fast) just use that. Why GDScript needs to compete with other languages in terms of speed?

This is my approach of using Godot:

  • Use GDExtension for API (Backend)
  • Extend the functionalities in GDScript (Frontend)

I think that simply just improving the VM is the best and "easiest" way of going forward with GDScript. Some of the users complain about the game shipping with the interpreter and parser for the GDScript, but how big is that exactly? I don't think there is a significance amount. In future maybe there could be an intermediary representation for the language in order to make the game ship with a simpler parser and smaller interpreter? Right now there are tons of optimization that can be made for the VM so there is room for improvements.

Indeed, speed is not the concern for most GDscript users I think, but I do think the fact that Godot compiles GDscript to plain text is an issue... which allows others with bad intention to open the game in Godot Engine as if the project is fully open source.

C# does not fix this issue, and C++ is super complex.

I wish Godot supported Swift instead of C#, as Miguel states, C# and its GC is a billion dollar mistake in gaming industry.

@Lcbx
Copy link

Lcbx commented Apr 14, 2024

You are probably saying this after watching Miguel de Icaza's video or something related.

C# support was a political move iirc ; so Unity users would migrate to Godot. I believe it has had good results on that front.
As a language, it is also a decent tradeoff between easy scripting and performance, and the GC is not that bad nowadays.

Swift is more of a c++ equivalent that is more developer-friendly, and has a future for making GDextension modules :)

@kisg
Copy link

kisg commented Apr 14, 2024

@Qws

This is a bit offtopic here. If you are interested in Swift support, please open another proposal or start a discussion on another forum.

However, Swift is already supported today with SwiftGodot: https://github.com/migueldeicaza/SwiftGodot

You can even embed Godot into a SwiftUI app with it using the libgodot PR (now under review): https://github.com/migeran/libgodot_project

@SlugFiller
Copy link

You all do realize SwiftGodot already exists, right? C# currently exists as an optional module, but there are talks about porting it to a GDExtension instead, so it would be on identical footing to SwiftGodot. This is considered to be an improvement to the C# integration.

Mind you that there's a hidden disadvantage to using Swift over C#: C# compiles to CLI, which is in turn executed by the runtime available on each target platform (Mono, .Net Core). Only a single CLI build is necessary, since CLI is an intermediate language.
By contrast, Swift requires a per-target build. Cross compilation is coming around Swift 5.11, so currently, such a build requires actually having each of the target platforms (that means owning both a PC and Mac). Even Swift's closest with to an IR target, WebAssembly, is only available on Mac host at the moment.

Basically, it boils down to: Swift is still too new, and cross-platform support is still incomplete.

@petar-dambovaliev

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests