Skip to content

Custom shaders, metal render api, cull toggles, and probably more#1335

Draft
EmeraldLoc wants to merge 135 commits into
coop-deluxe:devfrom
EmeraldLoc:shader
Draft

Custom shaders, metal render api, cull toggles, and probably more#1335
EmeraldLoc wants to merge 135 commits into
coop-deluxe:devfrom
EmeraldLoc:shader

Conversation

@EmeraldLoc

Copy link
Copy Markdown
Collaborator

This is a big one

Shaders

For documentation on how shaders work, read the docs! This pr description will mainly go over all hooks and functions added along with the systems in place.

Shader parser, GLSlang, and SPIRV-Cross

To summarize, a parser will go through and add all relevant info to a Shaderstruct to be used for cross compiling with SPIRV-Cross and caching and setting uniforms at the proper locations. It also does strict sanitization to make sure there's no funny business that'll happen when cross compiling to DirectX or Metal.

Firstly, there are 2 parsers, one for general sanitization and adding explicit input locs, and output locs, the other for converting a shader from GLSL 410 to GLSL 450

The general sanitization parser works by going through and looking for uniforms, inputs, and outputs. It'll take those and get the data from it and add in hardcoded locations and store those in the shader (it does not store locations for uniforms). When finished it'll double check that all the inputs and outputs line up, and if it does it'll go on with its day.

The parser that converts a shader from GLSL 410 to GLSL 450 works quite easily, since GLSL 410 shader code can be assumed to work on GLSL 450, all we do is:

  1. Move all uniforms into a UBO, or Uniform Buffer Object, for use with cross compiling
  2. Give samplers a uniform binding index
  3. Change version number to 450 core

This is the parsers job, to get shader information from the shader code, and give necessary information to glslang and SPIRV-Cross and the render apis the necessary information for getting locations of things

SPIRV-Cross is used to cross compile a SPIRV shader to HLSL or MSL. Now SPIRV needs to be generated first before we can use SPIRV-Cross, and we use our 410 to 450 parser to get it in a format acceptable by Vulkan, which is a good strict standard to adhere by. After compiling the shader into SPIRV, we can use that in SPIRV Cross. During that process for either HLSL or MSL we reflect to get uniform data to be used for shaders. Our input and output locations are already good info so we don't need to reflect on that.

With that done a successful conversion has been made!

The Metal Render API

Metal is Apple's graphics API. Due to macOS being stuck at OpenGL 4.1, it was necessary for a metal renderer to be made to ensure it doesn't get stuck as a legacy render api (more on that below). The metal renderer follows a similar structure to DX11, implementing all required functions. It uses metal-cpp so the renderer is written in c++ rather than Obj-C (which sucks, I hate obj-c). The metal-cpp files were chosen to be inside the include directory, they should realistically be a submodule but I HATE submodules and it shouldn't need to be updated very often at all. Not a whole lot to write on this, everything works in the metal renderer, it's very fast, cool 👍

Legacy Render API's

Some render apis are simply too old to work properly. Computers that don't support at least OpenGL 4.1 or DirectX11 simply won't be able to run the game. Computers that support OpenGL 4.1 but not OpenGL 4.5 are on a legacy renderer unless they can use DirectX. Currently, this only entails that the NDC (normalized device coordinates) depth range is -1 to 1, rather than 0 to 1. This can cause issues in custom shaders. Shaders may check for this via the gfx_is_legacy_renderer function and account for it accordingly, but if you're stuck on a legacy renderer, time is running out.

Multiple frame passes

The game by default has 2 frame passes, one to draw the world, the other as a post process pass. The post process pass just renders the last pass. This is to allow Lua mods to edit the post process pass only for minor adjustments. Lua mods can create or remove frame passes, which allows Lua to do many advanced shader effects.

Lua API Changes

  • Add a load of color combiner constants to Lua
  • Add frame pass constants to Lua
  • Add HOOK_ON_REFRESH_SHADERS
  • Add HOOK_ON_VERTEX_SHADER_CREATE
  • Add HOOK_ON_FRAGMENT_SHADER_CREATE
  • Add HOOK_ON_POST_PROCESS_VERTEX_SHADER_CREATE
  • Add HOOK_ON_POST_PROCESS_FRAGMENT_SHADER_CREATE
  • Add HOOK_BEFORE_DRAW_GEOMETRY
  • Add HOOK_ON_DRAW_GEOMETRY
  • Add HOOK_ON_SET_SHADER_UNIFORMS
  • Add gfx_set_culling_enabled
  • Add gfx_is_culling_enabled
  • Add gfx_get_render_api_name
  • Add gfx_is_legacy_renderer
  • Add gfx_reload_shaders
  • Add gfx_color_combiner_get_features
  • Add gfx_shader_set_bool
  • Add gfx_shader_set_int
  • Add gfx_shader_set_float
  • Add gfx_shader_set_vec2
  • Add gfx_shader_set_vec3
  • Add gfx_shader_set_vec4
  • Add gfx_shader_set_mat4
  • Add gfx_shader_set_bool_array
  • Add gfx_shader_set_int_array
  • Add gfx_shader_set_float_array
  • Add gfx_shader_set_vec2_array
  • Add gfx_shader_set_vec3_array
  • Add gfx_shader_set_vec4_array
  • Add gfx_shader_set_mat4_array
  • Add gfx_shader_create_frame_pass
  • Add gfx_shader_remove_frame_pass
  • Add gfx_shader_get_frame_pass_viewport
  • Add gfx_shader_set_frame_pass_viewport
  • Add gfx_shader_set_frame_pass_draw_world
  • Add gfx_shader_get_current_frame_pass

See the shader documentation and examples on how some of these work

That's about it I think, I hope. A video of an advanced pixel light lighting system can be seen here

@Blockyyy

Blockyyy commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

WE ARE SO BACK

@theclashingfritz

Copy link
Copy Markdown
Contributor

This is actually really cool. And more importantly. The way the game currently renders stuff through a reimplementation of how SM64 rendered them is a limitation not only for mods but also for performance. At some point and it would be a lot of work. But allowing mods to customize the shaders for all of the objects would allow mods to take off to the stars.

@EmeraldLoc EmeraldLoc marked this pull request as draft July 8, 2026 19:27
@EmeraldLoc EmeraldLoc marked this pull request as ready for review July 9, 2026 18:50
AgentXLP and others added 2 commits July 10, 2026 00:17
* Add shader optimization level function (D3D11 only so far)

Also lowered LE_MAX_LIGHTS to 512 because not even Underworld DX goes above ~300

* Fix compilation warning

* Fix flat shaded normals, add geometry mode to cm

* Revert shader optimization level function
Performance improves on dx11 and metal with lower value, but worsens in opengl. The original value of 256 is a good middle ground
@EmeraldLoc EmeraldLoc marked this pull request as draft July 10, 2026 16:58
@EmeraldLoc

Copy link
Copy Markdown
Collaborator Author

Appears to still be issues with OpenGL so marking as draft for now

EmeraldLoc and others added 8 commits July 10, 2026 15:43
- Make uFrameCount an unsigned int
- Fix issues with blocky's CC changes
* Add shader optimization level function (D3D11 only so far)

Also lowered LE_MAX_LIGHTS to 512 because not even Underworld DX goes above ~300

* Fix compilation warning

* Fix flat shaded normals, add geometry mode to cm

* Revert shader optimization level function

* Expose geometry modes

* Update hooks.md and address a couple tiny things

* Whoops

* Remove this because it's stupid

I understood why I did this at the time but it's just hacky and why would you bake lighting into a level if all of those vertices are going to get lighting engine'd anyway under this configuration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants