From 7f936d761e8c4534a80fac0c72d26a0cca473e26 Mon Sep 17 00:00:00 2001 From: Lars Viklund Date: Wed, 5 Aug 2026 00:28:11 +1200 Subject: [PATCH 1/3] fix: accurate frame elision and draw performance We always resolve the command buffer hash before drawing any layers so there is no need for mid-draw bail logic. Replaced MurmurHash64A with xxHash's way faster XXH3 for command buffer hashing. Only consider textures used for layer geometry when determining frame completeness for elision. Command buffer command structs are now packed on 1-byte alignment and the command key has shrunk from int to u8. Getting rid of padding isn't crucial as subsequent identical frames should both touch the padding bytes in similar ways, but it's nice. --- engine/render/r_main.cpp | 188 +++++++++------------------------------ engine/render/r_main.h | 6 +- vcpkg.json | 1 + 3 files changed, 47 insertions(+), 148 deletions(-) diff --git a/engine/render/r_main.cpp b/engine/render/r_main.cpp index 1d46715f..a23c2685 100644 --- a/engine/render/r_main.cpp +++ b/engine/render/r_main.cpp @@ -25,8 +25,6 @@ #include #include -static uint64_t MurmurHash64A(void const* data, int len, uint64_t seed); - // ======= // Classes // ======= @@ -125,8 +123,9 @@ Mat4 OrthoMatrix(double left, double right, double bottom, double top, double ne // Layer queue class // ================= +#pragma pack(push, r_layerCmd, 1) struct r_layerCmd_s { - enum Command { + enum Command : uint8_t { VIEWPORT, BLEND, BIND, @@ -165,6 +164,7 @@ struct r_layerCmdQuad_s { int stackLayer, maskLayer; } quad; }; +#pragma pack(pop, r_layerCmd) r_layer_c::r_layer_c(r_renderer_c* renderer, int layer, int subLayer) : renderer(renderer), layer(layer), subLayer(subLayer) @@ -418,6 +418,7 @@ struct RenderStrategy { virtual void ProcessCommand(r_layerCmd_s* cmd) = 0; virtual void Flush() = 0; virtual void SetShowStats(bool showStats) { showStats_ = showStats; } + virtual bool UsedIncompleteTextures() const { return false; } protected: bool showStats_{}; @@ -572,6 +573,8 @@ struct AdjacentMergeStrategy : RenderStrategy { } } + bool UsedIncompleteTextures() const override { return usedIncompleteTextures; }; + private: void Dispatch() { glBindBuffer(GL_ARRAY_BUFFER, vbo_); @@ -623,7 +626,10 @@ struct AdjacentMergeStrategy : RenderStrategy { auto tex = textures[i]; tex->Bind(); if (showStats_) { - ImGui::Text("New tex %d (%s)", tex->texId, tex->fileName.c_str()); + ImGui::Text("New tex %d (%s) %d", tex->texId, tex->fileName.c_str(), tex->status.load()); + } + if (!usedIncompleteTextures && tex->status != r_tex_c::Status::DONE) { + usedIncompleteTextures = true; } } else { @@ -655,7 +661,7 @@ struct AdjacentMergeStrategy : RenderStrategy { struct TexturedBatch { explicit TexturedBatch(GLuint prog) : batch(prog) { - textures.reserve(1ull << 20); + textures.reserve(128); } BatchKey key{}; @@ -673,9 +679,11 @@ struct AdjacentMergeStrategy : RenderStrategy { size_t totalVertexCount_ = 0; size_t batchIndex = 0; + + bool usedIncompleteTextures = false; }; -void r_layer_c::Render() +bool r_layer_c::Render() { int const optLevel = renderer->r_layerOptimize->intVal; bool const shuffle = renderer->r_layerShuffle->intVal == 1; @@ -713,6 +721,8 @@ void r_layer_c::Render() if (renderer->glPopGroupMarkerEXT) { renderer->glPopGroupMarkerEXT(); } + + return strat->UsedIncompleteTextures(); } void r_layer_c::Discard() @@ -877,6 +887,8 @@ void r_renderer_c::Init(r_featureFlag_e features) { sys->con->PrintFunc("Render Init"); + frameHashState.reset(XXH3_createState(), XXH3_freeState); + apiDpiAware = !!(features & F_DPI_AWARE); timer_c timer; @@ -1150,12 +1162,6 @@ void r_renderer_c::Shutdown() void r_renderer_c::PumpShaders() { texMan->ProcessPendingTextureUploads(); - for (size_t idx = 0; idx < numShader; ++idx) - if (auto* sh = shaderList[idx]) - if (auto tex = sh->tex; tex && tex->status != r_tex_c::DONE) { - inhibitElision = true; - break; - } } void r_renderer_c::BeginFrame() @@ -1331,20 +1337,18 @@ void r_renderer_c::EndFrame() ImGui::EndDisabled(); CVarCheckbox("Draw command culling", r_drawCull); - size_t totalFootprint{}, totalDenseFootprint{}; + size_t totalDenseFootprint{}; for (int l = 0; l < numLayer; ++l) { size_t byteAcc{}; auto layer = layerSort[l]; size_t const numCmd = layer->numCmd; - totalFootprint += numCmd * sizeof(r_layerCmdQuad_s); // legacy footprint totalDenseFootprint += layer->cmdCursor; } - ImGui::Text("Total payload footprint: %sB", BinaryUnitPrefix(totalFootprint).c_str()); ImGui::Text("Total dense footprint: %sB", BinaryUnitPrefix(totalDenseFootprint).c_str()); size_t totalCmd{}; - if (ImGui::BeginTable("Layer stats", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + if (ImGui::BeginTable("Layer stats", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("Index"); ImGui::TableSetupColumn("Layer"); ImGui::TableSetupColumn("Sublayer"); @@ -1383,83 +1387,46 @@ void r_renderer_c::EndFrame() if (inhibitElision || elideFrames != !!r_elideFrames->intVal) { elideFrames = !!r_elideFrames->intVal; - lastFrameHash.clear(); + lastFrameHash = 0; } - std::future>> elidedFrameHashFut; + auto tic = std::chrono::high_resolution_clock::now(); + + uint64_t commandDigest = 0; if (elideFrames) { - elidedFrameHashFut = std::async([&]() -> std::optional> { - std::vector commandDigest; - - for (auto lIdx = 0; lIdx < numLayer; ++lIdx) { - auto layer = layerSort[lIdx]; - uint64_t subHash = MurmurHash64A(layer->cmdStorage.data(), (int)layer->cmdCursor, 0ull); - uint8_t const* p = (uint8_t const*)&subHash; - commandDigest.insert(commandDigest.end(), p, p + sizeof(subHash)); - } + auto* frameHasher = frameHashState.get(); + XXH3_64bits_reset(frameHasher); - return commandDigest; - }); - } - else { - std::promise>> p; - elidedFrameHashFut = p.get_future(); - p.set_value({}); - } + for (auto lIdx = 0; lIdx < numLayer; ++lIdx) { + auto layer = layerSort[lIdx]; + uint64_t subHash = XXH3_64bits(layer->cmdStorage.data(), (int)layer->cmdCursor); + XXH3_64bits_update(frameHasher, &subHash, sizeof(subHash)); + } - elidedFrameHashFut.wait(); + commandDigest = XXH3_64bits_digest(frameHasher); + } ++totalFrames; - bool decideDraw = false; - bool elideDraw = false; + const bool elideDraw = lastFrameHash != 0 && lastFrameHash == commandDigest; + if (!elideDraw) { glBindFramebuffer(GL_FRAMEBUFFER, GetDrawRenderTarget().framebuffer); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - int l{}; - for (l = 0; l < numLayer; l++) { - if (!decideDraw && elidedFrameHashFut.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { - decideDraw = true; - auto commandDigest = elidedFrameHashFut.get(); - if (commandDigest) { - if (*commandDigest == lastFrameHash) { - elideDraw = true; - break; - } - else { - lastFrameHash = *commandDigest; - } - } - else { - lastFrameHash.clear(); - } - } + for (int l = 0; l < numLayer; l++) { auto& layer = layerSort[l]; if (layerBreak && layerBreak->first == layer->layer && layerBreak->second == layer->subLayer) { #ifdef _WIN32 DebugBreak(); #endif } - layer->Render(); - } - if (!elideDraw) { - presentRtt = 1 - presentRtt; - ++drawnFrames; - } - } - - if (!decideDraw) { - if (auto commandDigest = elidedFrameHashFut.get()) { - lastFrameHash = *commandDigest; - } - else { - lastFrameHash.clear(); + inhibitElision = layer->Render() || inhibitElision; } + presentRtt = 1 - presentRtt; + ++drawnFrames; } - if (inhibitElision) { - // If we explicitly inhibited elision due to things like incomplete textures, make sure that the next frame is drawn. - lastFrameHash.clear(); - } + // If we explicitly inhibited elision due to things like incomplete textures, make sure that the next frame is drawn. + lastFrameHash = inhibitElision ? 0 : commandDigest; for (int l = 0; l < numLayer; ++l) { layerSort[l]->Discard(); @@ -1500,7 +1467,7 @@ void r_renderer_c::EndFrame() if (ImGui::Begin("Hash")) { char* b64{}; size_t b64Len{}; - Base64UrlEncode((char const*)lastFrameHash.data(), lastFrameHash.size(), &b64, &b64Len); + Base64UrlEncode((char const*)&lastFrameHash, sizeof(lastFrameHash), &b64, &b64Len); ImGui::Text("%s", b64); free(b64); } @@ -2010,74 +1977,3 @@ r_renderer_c::RenderTarget& r_renderer_c::GetPresentRenderTarget() { return rttMain[presentRtt]; } - -// =========================================================== -// MurmurHash implementation from public domain, obtained from -// https://github.com/explosion/murmurhash/blob/9281c4825c24e64476457db89fb1d39bf09b3d23/murmurhash/MurmurHash2.cpp -// =========================================================== - -#if _WIN32 -#define BIG_CONSTANT(x) (x) -#else -#define BIG_CONSTANT(x) (x##LLU) -#endif - -static inline uint64_t MurmurHashGetBlock(const uint64_t* p) -{ -#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - return *p; -#else - const uint8_t* c = (const uint8_t*)p; - return (uint64_t)c[0] | - (uint64_t)c[1] << 8 | - (uint64_t)c[2] << 16 | - (uint64_t)c[3] << 24 | - (uint64_t)c[4] << 32 | - (uint64_t)c[5] << 40 | - (uint64_t)c[6] << 48 | - (uint64_t)c[7] << 56; -#endif -} - -uint64_t MurmurHash64A(const void* key, int len, uint64_t seed) -{ - const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); - const int r = 47; - - uint64_t h = seed ^ (len * m); - - const uint64_t* data = (const uint64_t*)key; - const uint64_t* end = data + (len / 8); - - while (data != end) - { - uint64_t k = MurmurHashGetBlock(data++); - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - } - - const unsigned char* data2 = (const unsigned char*)data; - - switch (len & 7) - { - case 7: h ^= uint64_t(data2[6]) << 48; - case 6: h ^= uint64_t(data2[5]) << 40; - case 5: h ^= uint64_t(data2[4]) << 32; - case 4: h ^= uint64_t(data2[3]) << 24; - case 3: h ^= uint64_t(data2[2]) << 16; - case 2: h ^= uint64_t(data2[1]) << 8; - case 1: h ^= uint64_t(data2[0]); - h *= m; - }; - - h ^= h >> r; - h *= m; - h ^= h >> r; - - return h; -} diff --git a/engine/render/r_main.h b/engine/render/r_main.h index f7072fb4..876a50ca 100644 --- a/engine/render/r_main.h +++ b/engine/render/r_main.h @@ -15,6 +15,7 @@ #include #include #include +#include // ======= // Classes @@ -46,7 +47,7 @@ class r_layer_c { void Bind(r_tex_c* tex); void Color(col4_t col); void Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer = 0, int maskLayer = -1); - void Render(); + bool Render(); void Discard(); struct CmdHandle { @@ -180,7 +181,8 @@ class r_renderer_c: public r_IRenderer, public conCmdHandler_c { RenderTarget rttMain[2]; int presentRtt = 0; - std::vector lastFrameHash{}; + std::shared_ptr frameHashState; + uint64_t lastFrameHash{}; uint64_t totalFrames{}; uint64_t drawnFrames{}; diff --git a/vcpkg.json b/vcpkg.json index 2822c6d3..13380ba0 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -14,6 +14,7 @@ "pkgconf", "re2", "sol2", + "xxhash", "zstd", "zlib" ] From d58b620397a0941102a4a1c0fd26bbff89aa2f4d Mon Sep 17 00:00:00 2001 From: Lars Viklund Date: Wed, 9 Sep 2026 13:38:30 +1200 Subject: [PATCH 2/3] feat: move shader sources to build-embedded files By moving the hardcoded shader sources to standalone files that can be included at build time, we can get a better editing experience with language servers in external editors. This without having to ship loose shader files with the runtime as they're embedded at build time with CMakeRC. --- CMakeLists.txt | 60 ++++++----- assets/gles/display_render_target.frag | 13 +++ assets/gles/display_render_target.vert | 10 ++ assets/gles/tinted_texture.frag | 26 +++++ assets/gles/tinted_texture.vert | 30 ++++++ engine/render/r_main.cpp | 131 +++++++------------------ vcpkg.json | 1 + 7 files changed, 149 insertions(+), 122 deletions(-) create mode 100644 assets/gles/display_render_target.frag create mode 100644 assets/gles/display_render_target.vert create mode 100644 assets/gles/tinted_texture.frag create mode 100644 assets/gles/tinted_texture.vert diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d15a17d..12d2ccf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,23 @@ include(${PROJECT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ".") include(InstallRequiredSystemLibraries) +find_package(unofficial-angle CONFIG REQUIRED) +find_package(CMakeRC CONFIG REQUIRED) +find_package(CURL CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) +find_package(glfw3 CONFIG REQUIRED) +find_package(gli CONFIG REQUIRED) +find_package(glm CONFIG REQUIRED) +find_package(LuaJIT REQUIRED) +find_package(Microsoft.GSL CONFIG REQUIRED) +find_package(PkgConfig REQUIRED) +find_package(re2 CONFIG REQUIRED) +find_package(sol2 CONFIG REQUIRED) +find_package(Threads REQUIRED) +find_package(zstd REQUIRED) +find_package(ZLIB REQUIRED) +find_package(WebP) + set(SIMPLEGRAPHIC_SOURCES "config.h" "dep/stb/stb_image.h" @@ -75,28 +92,36 @@ set(SIMPLEGRAPHIC_SOURCES "ui_subscript.h" ) -set (SIMPLEGRAPHIC_PLATFORM_SOURCES) +add_library(SimpleGraphic SHARED + ${SIMPLEGRAPHIC_SOURCES} +) + +cmrc_add_resource_library(SimpleGraphic-Assets ALIAS SimpleGraphic::Assets NAMESPACE SimpleGraphic) + +cmrc_add_resources(SimpleGraphic-Assets + "assets/gles/display_render_target.frag" + "assets/gles/display_render_target.vert" + "assets/gles/tinted_texture.frag" + "assets/gles/tinted_texture.vert" +) + if (APPLE) - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + target_sources(SimpleGraphic PRIVATE "engine/system/win/sys_macos.mm" ) endif() if (WIN32) - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + target_sources(SimpleGraphic PRIVATE "engine/system/win/sys_console.cpp" "SimpleGraphic.rc" ) else() - set (SIMPLEGRAPHIC_PLATFORM_SOURCES + target_sources(SimpleGraphic PRIVATE "engine/system/win/sys_console_unix.cpp" ) endif() -add_library(SimpleGraphic SHARED - ${SIMPLEGRAPHIC_SOURCES} - ${SIMPLEGRAPHIC_PLATFORM_SOURCES} -) target_compile_definitions(SimpleGraphic PRIVATE "_CRT_SECURE_NO_DEPRECATE" @@ -111,28 +136,14 @@ target_compile_definitions(SimpleGraphic "UNICODE" ) +target_compile_options(SimpleGraphic PRIVATE "$<$:/MP>") + target_include_directories(SimpleGraphic PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/engine" ) -find_package(unofficial-angle CONFIG REQUIRED) -find_package(CURL CONFIG REQUIRED) -find_package(fmt CONFIG REQUIRED) -find_package(glfw3 CONFIG REQUIRED) -find_package(gli CONFIG REQUIRED) -find_package(glm CONFIG REQUIRED) -find_package(LuaJIT REQUIRED) -find_package(Microsoft.GSL CONFIG REQUIRED) -find_package(PkgConfig REQUIRED) -find_package(re2 CONFIG REQUIRED) -find_package(sol2 CONFIG REQUIRED) -find_package(Threads REQUIRED) -find_package(zstd REQUIRED) -find_package(ZLIB REQUIRED) -find_package(WebP) - add_library(cmp_core STATIC dep/compressonator/cmp_core/source/cmp_core.cpp dep/compressonator/cmp_core/source/cmp_core.h @@ -239,6 +250,7 @@ target_link_libraries(SimpleGraphic LuaJIT::LuaJIT Microsoft.GSL::GSL re2::re2 + SimpleGraphic::Assets Threads::Threads WebP::webpdecoder ZLIB::ZLIB diff --git a/assets/gles/display_render_target.frag b/assets/gles/display_render_target.frag new file mode 100644 index 00000000..0a12096c --- /dev/null +++ b/assets/gles/display_render_target.frag @@ -0,0 +1,13 @@ +#version 300 es +precision mediump float; + +uniform highp sampler2D s_tex; + +in vec2 v_texcoord; + +out vec4 f_fragColor; + +void main(void) { + vec3 color = texture(s_tex, v_texcoord).rgb; + f_fragColor = vec4(color, 1.0); +} diff --git a/assets/gles/display_render_target.vert b/assets/gles/display_render_target.vert new file mode 100644 index 00000000..190d72ac --- /dev/null +++ b/assets/gles/display_render_target.vert @@ -0,0 +1,10 @@ +#version 300 es +in vec4 a_position; +in vec2 a_texcoord; + +out vec2 v_texcoord; + +void main(void) { + gl_Position = a_position; + v_texcoord = a_texcoord; +} diff --git a/assets/gles/tinted_texture.frag b/assets/gles/tinted_texture.frag new file mode 100644 index 00000000..1c9e9dfe --- /dev/null +++ b/assets/gles/tinted_texture.frag @@ -0,0 +1,26 @@ +#version 300 es +precision mediump float; + +uniform highp sampler2DArray s_tex[{SG_TEXTURE_COUNT}]; + +in vec2 v_screenPos; +in vec2 v_texcoord; +in vec4 v_tint; +in vec4 v_viewport; // x0, y0, x1, y1 +in vec3 v_texId; + +out vec4 f_fragColor; + +void main(void) +{{ + float x = v_screenPos[0], y = v_screenPos[1]; + if (x < v_viewport[0] || + y < v_viewport[1] || + x >= v_viewport[2] || + y >= v_viewport[3]) {{ + discard; + }} + vec4 color; + {SG_TEXTURE_SWITCH} + f_fragColor = color * v_tint; +}} diff --git a/assets/gles/tinted_texture.vert b/assets/gles/tinted_texture.vert new file mode 100644 index 00000000..1dc83d98 --- /dev/null +++ b/assets/gles/tinted_texture.vert @@ -0,0 +1,30 @@ +#version 300 es + +uniform mat4 mvp_matrix; + +in vec2 a_vertex; +in vec2 a_texcoord; +in vec4 a_tint; +in vec4 a_viewport; +in vec3 a_texId; + +out vec2 v_screenPos; +out vec2 v_texcoord; +out vec4 v_tint; +out vec4 v_viewport; +out vec3 v_texId; + +void main(void) +{ + v_texcoord = a_texcoord; + v_tint = a_tint; + v_texId = a_texId; + vec2 vp0 = a_viewport.xy + vec2(0.0, a_viewport.w); + vec2 vp1 = a_viewport.xy + vec2(a_viewport.z, 0.0); + v_viewport = vec4( + (mvp_matrix * vec4(vp0, 0.0, 1.0)).xy, + (mvp_matrix * vec4(vp1, 0.0, 1.0)).xy); + vec4 pos = mvp_matrix * vec4(a_vertex + a_viewport.xy, 0.0, 1.0); + v_screenPos = pos.xy; + gl_Position = pos; +} diff --git a/engine/render/r_main.cpp b/engine/render/r_main.cpp index a23c2685..373ea7da 100644 --- a/engine/render/r_main.cpp +++ b/engine/render/r_main.cpp @@ -21,10 +21,13 @@ #include #include +#include #include #include #include +CMRC_DECLARE(SimpleGraphic); + // ======= // Classes // ======= @@ -791,102 +794,25 @@ static std::string GetProgramInfoLog(GLuint id) return std::string(msg.data(), msg.data() + len); } -static char const* s_tintedTextureVertexSource = R"(#version 300 es - -uniform mat4 mvp_matrix; - -in vec2 a_vertex; -in vec2 a_texcoord; -in vec4 a_tint; -in vec4 a_viewport; -in vec3 a_texId; - -out vec2 v_screenPos; -out vec2 v_texcoord; -out vec4 v_tint; -out vec4 v_viewport; -out vec3 v_texId; - -void main(void) -{ - v_texcoord = a_texcoord; - v_tint = a_tint; - v_texId = a_texId; - vec2 vp0 = a_viewport.xy + vec2(0.0, a_viewport.w); - vec2 vp1 = a_viewport.xy + vec2(a_viewport.z, 0.0); - v_viewport = vec4( - (mvp_matrix * vec4(vp0, 0.0, 1.0)).xy, - (mvp_matrix * vec4(vp1, 0.0, 1.0)).xy); - vec4 pos = mvp_matrix * vec4(a_vertex + a_viewport.xy, 0.0, 1.0); - v_screenPos = pos.xy; - gl_Position = pos; -} -)"; - -static char const* s_tintedTextureFragmentTemplate = R"(#version 300 es -precision mediump float; - -uniform highp sampler2DArray s_tex[{SG_TEXTURE_COUNT}]; -uniform vec4 i_tint; - -in vec2 v_screenPos; -in vec2 v_texcoord; -in vec4 v_tint; -in vec4 v_viewport; // x0, y0, x1, y1 -in vec3 v_texId; - -out vec4 f_fragColor; - -void main(void) -{{ - float x = v_screenPos[0], y = v_screenPos[1]; - if (x < v_viewport[0] || - y < v_viewport[1] || - x >= v_viewport[2] || - y >= v_viewport[3]) {{ - discard; - }} - vec4 color; - {SG_TEXTURE_SWITCH} - f_fragColor = color * v_tint; -}} -)"; - -std::string const s_scaleVsSource = R"(#version 300 es -in vec4 a_position; -in vec2 a_texcoord; - -out vec2 v_texcoord; +// ============= +// Init/Shutdown +// ============= -void main(void) { - gl_Position = a_position; - v_texcoord = a_texcoord; +static std::tuple ResourceViewOpenGL(cmrc::file file) +{ + return {(const GLchar*)file.begin(), (GLint)file.size()}; } -)"; - -std::string const s_scaleFsSource = R"(#version 300 es -precision mediump float; - -uniform highp sampler2D s_tex; -in vec2 v_texcoord; - -out vec4 f_fragColor; - -void main(void) { - vec3 color = texture(s_tex, v_texcoord).rgb; - f_fragColor = vec4(color, 1.0); +static std::string_view ResourceStringView(cmrc::file file) +{ + return std::string_view(file.begin(), file.size()); } -)"; - -// ============= -// Init/Shutdown -// ============= void r_renderer_c::Init(r_featureFlag_e features) { sys->con->PrintFunc("Render Init"); + const auto resources = cmrc::SimpleGraphic::get_filesystem(); frameHashState.reset(XXH3_createState(), XXH3_freeState); apiDpiAware = !!(features & F_DPI_AWARE); @@ -971,7 +897,8 @@ void r_renderer_c::Init(r_featureFlag_e features) GLint success = GL_FALSE; GLuint prog = glCreateProgram(); GLuint vs = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vs, 1, &s_tintedTextureVertexSource, nullptr); + auto [tintVertexSourceText, tintVertexSourceLen] = ResourceViewOpenGL(resources.open("assets/gles/tinted_texture.vert")); + glShaderSource(vs, 1, &tintVertexSourceText, &tintVertexSourceLen); glCompileShader(vs); if (!GetShaderCompileSuccess(vs)) { std::string log = GetShaderInfoLog(vs); @@ -1000,11 +927,17 @@ void r_renderer_c::Init(r_featureFlag_e features) } textureSwitch = to_string(buf); } - std::string fragSource = fmt::format(s_tintedTextureFragmentTemplate, - fmt::arg("SG_TEXTURE_COUNT", maxTextureImageUnits), - fmt::arg("SG_TEXTURE_SWITCH", textureSwitch)); - char const* fragSourcePtr = fragSource.c_str(); - glShaderSource(fs, 1, &fragSourcePtr, nullptr); + const auto tintFragmentTemplate = ResourceStringView(resources.open("assets/gles/tinted_texture.frag")); + try { + std::string fragSource = fmt::format(fmt::runtime(tintFragmentTemplate), + fmt::arg("SG_TEXTURE_COUNT", maxTextureImageUnits), + fmt::arg("SG_TEXTURE_SWITCH", textureSwitch)); + char const* fragSourcePtr = fragSource.c_str(); + glShaderSource(fs, 1, &fragSourcePtr, nullptr); + } + catch (std::exception& e) { + sys->Error(u8"Failed to format fragment shader:\n%s", e.what()); + } glCompileShader(fs); if (!GetShaderCompileSuccess(fs)) { std::string log = GetShaderInfoLog(fs); @@ -1054,12 +987,12 @@ void r_renderer_c::Init(r_featureFlag_e features) return id; }; - auto vsId = compileShader(s_scaleVsSource, GL_VERTEX_SHADER); + auto vsId = compileShader(ResourceStringView(resources.open("assets/gles/display_render_target.vert")), GL_VERTEX_SHADER); if (!GetShaderCompileSuccess(vsId)) { auto log = GetShaderInfoLog(vsId); sys->con->Printf("Scaling VS compile failure: %s\n", log.c_str()); } - auto fsId = compileShader(s_scaleFsSource, GL_FRAGMENT_SHADER); + auto fsId = compileShader(ResourceStringView(resources.open("assets/gles/display_render_target.frag")), GL_FRAGMENT_SHADER); if (!GetShaderCompileSuccess(fsId)) { auto log = GetShaderInfoLog(fsId); sys->con->Printf("Scaling FS compile failure: %s\n", log.c_str()); @@ -1115,9 +1048,11 @@ void r_renderer_c::Shutdown() sys->con->Printf("Unloading resources...\n"); - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(imguiCtx); + if (imguiCtx) { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(imguiCtx); + } delete whiteImage; diff --git a/vcpkg.json b/vcpkg.json index 13380ba0..cdd3e3b0 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "version": "2.5", "dependencies": [ "angle", + "cmakerc", "curl", "fmt", "glfw3", From c6d2046015e815b71fef1a791ecd890c3642e7cf Mon Sep 17 00:00:00 2001 From: Lars Viklund Date: Wed, 9 Sep 2026 14:48:36 +1200 Subject: [PATCH 3/3] feat: drop BlendMode and mask index, optimise GLSL The blend mode and mask index have been removed from the runtime and Lua API, and the shader now perform a much cheaper mapping from virtual to NDC coordinates; inspired by rusty-path-of-building. The blend mode has been modifiable via calls from Lua since the beginning but has in modern time not been set to anything but regular alpha blending. Draw calls have been able to pass a mask layer as an optional parameter, intended for blending out regions of the color texture that the app did not want to display, like only drawing the immediate areas around line connectors with the new style of nested connector textures. ANGLE shaders performed a full 4x4 matrix transformation of positions with a model-view-projection matrix, making it capable of performing arbitrary transforms of geometry. This was costly in terms of both uniform buffers but also added shader complexity and ALU cost. It also did not play nice with CPU draw call culling. --- assets/gles/tinted_texture.frag | 10 +- assets/gles/tinted_texture.vert | 24 ++-- engine/render.h | 13 +- engine/render/r_main.cpp | 204 ++++++-------------------------- engine/render/r_main.h | 30 +++-- ui_api.cpp | 95 +++------------ 6 files changed, 90 insertions(+), 286 deletions(-) diff --git a/assets/gles/tinted_texture.frag b/assets/gles/tinted_texture.frag index 1c9e9dfe..7bc4315a 100644 --- a/assets/gles/tinted_texture.frag +++ b/assets/gles/tinted_texture.frag @@ -6,20 +6,12 @@ uniform highp sampler2DArray s_tex[{SG_TEXTURE_COUNT}]; in vec2 v_screenPos; in vec2 v_texcoord; in vec4 v_tint; -in vec4 v_viewport; // x0, y0, x1, y1 -in vec3 v_texId; +in vec2 v_texId; out vec4 f_fragColor; void main(void) {{ - float x = v_screenPos[0], y = v_screenPos[1]; - if (x < v_viewport[0] || - y < v_viewport[1] || - x >= v_viewport[2] || - y >= v_viewport[3]) {{ - discard; - }} vec4 color; {SG_TEXTURE_SWITCH} f_fragColor = color * v_tint; diff --git a/assets/gles/tinted_texture.vert b/assets/gles/tinted_texture.vert index 1dc83d98..cc101888 100644 --- a/assets/gles/tinted_texture.vert +++ b/assets/gles/tinted_texture.vert @@ -1,30 +1,28 @@ #version 300 es -uniform mat4 mvp_matrix; +uniform vec2 u_screenSize; in vec2 a_vertex; in vec2 a_texcoord; in vec4 a_tint; -in vec4 a_viewport; -in vec3 a_texId; +in vec2 a_texId; out vec2 v_screenPos; out vec2 v_texcoord; out vec4 v_tint; -out vec4 v_viewport; -out vec3 v_texId; +out vec2 v_texId; + +vec2 SceneToNdc(vec2 scenePos) { + vec2 unitPos = scenePos / u_screenSize; + return mix(vec2(-1.0, 1.0), vec2(1.0, -1.0), unitPos); +} void main(void) { v_texcoord = a_texcoord; v_tint = a_tint; v_texId = a_texId; - vec2 vp0 = a_viewport.xy + vec2(0.0, a_viewport.w); - vec2 vp1 = a_viewport.xy + vec2(a_viewport.z, 0.0); - v_viewport = vec4( - (mvp_matrix * vec4(vp0, 0.0, 1.0)).xy, - (mvp_matrix * vec4(vp1, 0.0, 1.0)).xy); - vec4 pos = mvp_matrix * vec4(a_vertex + a_viewport.xy, 0.0, 1.0); - v_screenPos = pos.xy; - gl_Position = pos; + vec2 pos = SceneToNdc(a_vertex); + v_screenPos = pos; + gl_Position = vec4(pos, 0, 1); } diff --git a/engine/render.h b/engine/render.h index 0bb36813..4b6aa7ae 100644 --- a/engine/render.h +++ b/engine/render.h @@ -5,6 +5,7 @@ // #include +#include // ======= // Classes @@ -44,13 +45,6 @@ enum r_texFlag_e { TF_ASYNC = 0x08, // Load asynchronously }; -// Blend modes -enum r_blendMode_e { - RB_ALPHA, - RB_PRE_ALPHA, - RB_ADDITIVE -}; - // Shader handle class r_shaderHnd_c { friend class r_renderer_c; @@ -93,12 +87,11 @@ class r_IRenderer { virtual void SetDrawSubLayer(int subLayer) = 0; virtual int GetDrawLayer() = 0; virtual void SetViewport(int x = 0, int y = 0, int width = 0, int height = 0) = 0; - virtual void SetBlendMode(int mode) = 0; virtual void DrawColor(const col4_t col = NULL) = 0; virtual void DrawColor(dword col) = 0; virtual void GetDrawColor(col4_t color) = 0; - virtual void DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1 = { 0, 0 }, glm::vec2 uv2 = { 1, 1 }, int stackLayer = 0, std::optional maskLayer = {}) = 0; - virtual void DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0 = { 0, 0 }, glm::vec2 uv1 = { 1, 0 }, glm::vec2 uv2 = { 1, 1 }, glm::vec2 uv3 = { 0, 1 }, int stackLayer = 0, std::optional maskLayer = {}) = 0; + virtual void DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1 = { 0, 0 }, glm::vec2 uv2 = { 1, 1 }, int stackLayer = 0) = 0; + virtual void DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0 = { 0, 0 }, glm::vec2 uv1 = { 1, 0 }, glm::vec2 uv2 = { 1, 1 }, glm::vec2 uv3 = { 0, 1 }, int stackLayer = 0) = 0; virtual void DrawString(float x, float y, int align, int height, const col4_t col, int font, const char* str) = 0; virtual void DrawStringFormat(float x, float y, int align, int height, const col4_t col, int font, const char* fmt, ...) = 0; virtual int DrawStringWidth(int height, int font, const char* str) = 0; diff --git a/engine/render/r_main.cpp b/engine/render/r_main.cpp index 373ea7da..9b4fb843 100644 --- a/engine/render/r_main.cpp +++ b/engine/render/r_main.cpp @@ -100,28 +100,6 @@ r_shaderHnd_c::~r_shaderHnd_c() } } -struct Mat4 { - float m[16]; - - float const* data() const { - return m; - } -}; - -Mat4 OrthoMatrix(double left, double right, double bottom, double top, double nearVal, double farVal) -{ - Mat4 ret; - std::fill_n(ret.m, 16, 0.0f); - ret.m[0] = (float)(2.0f / (right - left)); - ret.m[5] = (float)(2.0f / (top - bottom)); - ret.m[10] = (float)(-2.0f / (farVal - nearVal)); - ret.m[12] = (float)-((right + left) / (right - left)); - ret.m[13] = (float)-((top + bottom) / (top - bottom)); - ret.m[14] = (float)-((farVal + nearVal) / (farVal - nearVal)); - ret.m[15] = 1.0f; - return ret; -} - // ================= // Layer queue class // ================= @@ -130,7 +108,6 @@ Mat4 OrthoMatrix(double left, double right, double bottom, double top, double ne struct r_layerCmd_s { enum Command : uint8_t { VIEWPORT, - BLEND, BIND, COLOR, QUAD, @@ -142,11 +119,6 @@ struct r_layerCmdViewport_s { r_viewport_s viewport; }; -struct r_layerCmdBlend_s { - r_layerCmd_s::Command cmd; - int blendMode; -}; - struct r_layerCmdBind_s { r_layerCmd_s::Command cmd; r_tex_c* tex; @@ -164,7 +136,7 @@ struct r_layerCmdQuad_s { float t[4]; float x[4]; float y[4]; - int stackLayer, maskLayer; + int stackLayer; } quad; }; #pragma pack(pop, r_layerCmd) @@ -185,7 +157,6 @@ static size_t CommandSize(r_layerCmd_s::Command cmd, size_t extraSize = 0) { using Tag = r_layerCmd_s::Command; switch (cmd) { case Tag::VIEWPORT: return sizeof(r_layerCmdViewport_s); - case Tag::BLEND: return sizeof(r_layerCmdBlend_s); case Tag::BIND: return sizeof(r_layerCmdBind_s); case Tag::COLOR: return sizeof(r_layerCmdColor_s); case Tag::QUAD: return sizeof(r_layerCmdQuad_s); @@ -234,18 +205,7 @@ void r_layer_c::SetViewport(r_viewport_s* viewport) { if (auto* cmd = (r_layerCmdViewport_s*)NewCommand(CommandSize(r_layerCmd_s::VIEWPORT))) { cmd->cmd = r_layerCmd_s::VIEWPORT; - cmd->viewport.x = viewport->x; - cmd->viewport.y = viewport->y; - cmd->viewport.width = viewport->width; - cmd->viewport.height = viewport->height; - } -} - -void r_layer_c::SetBlendMode(int mode) -{ - if (auto* cmd = (r_layerCmdBlend_s*)NewCommand(CommandSize(r_layerCmd_s::BLEND))) { - cmd->cmd = r_layerCmd_s::BLEND; - cmd->blendMode = mode; + cmd->viewport = *viewport; } } @@ -265,7 +225,7 @@ void r_layer_c::Color(col4_t col) } } -void r_layer_c::Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer, int maskLayer) +void r_layer_c::Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer) { if (auto* cmd = (r_layerCmdQuad_s*)NewCommand(CommandSize(r_layerCmd_s::QUAD))) { cmd->cmd = r_layerCmd_s::QUAD; @@ -274,59 +234,14 @@ void r_layer_c::Quad(float s0, float t0, float x0, float y0, float s1, float t1, cmd->quad.x[0] = x0; cmd->quad.x[1] = x1; cmd->quad.x[2] = x2; cmd->quad.x[3] = x3; cmd->quad.y[0] = y0; cmd->quad.y[1] = y1; cmd->quad.y[2] = y2; cmd->quad.y[3] = y3; cmd->quad.stackLayer = stackLayer; - cmd->quad.maskLayer = maskLayer; - } -} - -// ================= -// Geometric queries -// ================= - -struct r_aabb_s { - float lo[2]; - float hi[2]; -}; - -r_aabb_s AabbFromCmdQuad(decltype(r_layerCmdQuad_s::quad)& q, r_viewport_s& vp) -{ - r_aabb_s r{ - {+FLT_MAX, +FLT_MAX}, - {-FLT_MAX, -FLT_MAX}, - }; - for (size_t i = 0; i < 4; ++i) { - r.lo[0] = (std::min)(r.lo[0], (float)q.x[i]); - r.lo[1] = (std::min)(r.lo[1], (float)q.y[i]); - r.hi[0] = (std::max)(r.hi[0], (float)q.x[i]); - r.hi[1] = (std::max)(r.hi[1], (float)q.y[i]); } - r.lo[0] += vp.x; - r.lo[1] += vp.y; - r.hi[0] += vp.x; - r.hi[1] += vp.y; - return r; -} - -r_aabb_s AabbFromViewport(r_viewport_s& vp) -{ - r_aabb_s r{ - {(float)vp.x, (float)vp.y }, - {(float)(vp.x + vp.width), (float)(vp.y + vp.height) }, - }; - return r; -} - -bool AabbAabbIntersects(r_aabb_s& a, r_aabb_s& b) -{ - // A.lo <= B.hi && A.hi >= B.lo - return a.lo[0] <= b.hi[0] && a.lo[1] <= b.hi[1] && a.hi[0] >= b.lo[0] && a.hi[1] >= b.lo[1]; } struct Vertex { float x, y; float u, v; float r, g, b, a; - float viewX, viewY, viewW, viewH; - float texId, stackIdx, maskIdx; + float texId, stackIdx; }; struct Batch { @@ -341,7 +256,6 @@ struct Batch { GLint xyAttr; GLint uvAttr; GLint tintAttr; - GLint viewportAttr; GLint texIdAttr; std::vector vertices; @@ -355,7 +269,6 @@ Batch::Batch(GLuint prog) xyAttr = glGetAttribLocation(prog, "a_vertex"); uvAttr = glGetAttribLocation(prog, "a_texcoord"); tintAttr = glGetAttribLocation(prog, "a_tint"); - viewportAttr = glGetAttribLocation(prog, "a_viewport"); texIdAttr = glGetAttribLocation(prog, "a_texId"); } @@ -364,7 +277,6 @@ Batch::Batch(Batch&& rhs) , xyAttr(rhs.xyAttr) , uvAttr(rhs.uvAttr) , tintAttr(rhs.tintAttr) - , viewportAttr(rhs.viewportAttr) , texIdAttr(rhs.texIdAttr) , vertices(std::move(rhs.vertices)) { @@ -375,7 +287,6 @@ Batch& Batch::operator = (Batch&& rhs) { xyAttr = rhs.xyAttr; uvAttr = rhs.uvAttr; tintAttr = rhs.tintAttr; - viewportAttr = rhs.viewportAttr; texIdAttr = rhs.texIdAttr; vertices = std::move(rhs.vertices); @@ -398,18 +309,15 @@ void Batch::Execute(GLuint sharedVbo, size_t vertexBase) glVertexAttribPointer(xyAttr, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, x)); glVertexAttribPointer(uvAttr, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, u)); glVertexAttribPointer(tintAttr, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, r)); - glVertexAttribPointer(viewportAttr, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, viewX)); - glVertexAttribPointer(texIdAttr, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, texId)); + glVertexAttribPointer(texIdAttr, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void const*)offsetof(Vertex, texId)); glEnableVertexAttribArray(xyAttr); glEnableVertexAttribArray(uvAttr); glEnableVertexAttribArray(tintAttr); - glEnableVertexAttribArray(viewportAttr); glEnableVertexAttribArray(texIdAttr); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.size()); glDisableVertexAttribArray(xyAttr); glDisableVertexAttribArray(uvAttr); glDisableVertexAttribArray(tintAttr); - glDisableVertexAttribArray(viewportAttr); glDisableVertexAttribArray(texIdAttr); glBindBuffer(GL_ARRAY_BUFFER, 0); vertices.clear(); @@ -427,12 +335,6 @@ struct RenderStrategy { bool showStats_{}; }; -static std::map const s_blendModeString{ - {RB_ALPHA, "RB_ALPHA"}, - {RB_PRE_ALPHA, "RB_PRE_ALPHA"}, - {RB_ADDITIVE, "RB_ADDITIVE"}, -}; - struct AdjacentMergeStrategy : RenderStrategy { AdjacentMergeStrategy(r_layer_c* layer, r_renderer_c* renderer, GLuint prog) : layer_(layer), renderer_(renderer), prog_(prog), batch_(prog) @@ -444,20 +346,22 @@ struct AdjacentMergeStrategy : RenderStrategy { } texLocs_.push_back(loc); } - mvpMatrixLoc_ = glGetUniformLocation(prog_, "mvp_matrix"); + uScreenSizeLoc_ = glGetUniformLocation(prog_, "u_screenSize"); batchTextureCap_ = texLocs_.size(); glGenBuffers(1, &vbo_); + glEnable(GL_SCISSOR_TEST); } ~AdjacentMergeStrategy() { + glDisable(GL_SCISSOR_TEST); glDeleteBuffers(1, &vbo_); } struct BatchKey { - int blendMode = -1; + r_viewport_s viewport{}; bool operator < (BatchKey const& rhs) const { - return blendMode < rhs.blendMode; + return viewport < rhs.viewport; } bool operator == (BatchKey const& rhs) const { @@ -473,18 +377,11 @@ struct AdjacentMergeStrategy : RenderStrategy { switch (cmd->cmd) { case r_layerCmd_s::VIEWPORT: { auto* c = (r_layerCmdViewport_s*)cmd; - nextViewport_ = c->viewport; + latchKey_.viewport = c->viewport; if (showStats_) { // ImGui::Text("VIEWPORT: %dx%d @ %d,%d", c->viewport.width, c->viewport.height, c->viewport.x, c->viewport.y); } } break; - case r_layerCmd_s::BLEND: { - auto* c = (r_layerCmdBlend_s*)cmd; - latchKey_.blendMode = c->blendMode; - if (showStats_) { - // ImGui::Text("BLEND: %s", s_blendModeString.at((r_blendMode_e)c->blendMode)); - } - } break; case r_layerCmd_s::BIND: { auto* c = (r_layerCmdBind_s*)cmd; nextTex_ = c->tex; @@ -502,14 +399,17 @@ struct AdjacentMergeStrategy : RenderStrategy { // ImGui::Text("QUAD"); } + const auto vp = latchKey_.viewport; + // Cull the quad first before it influences any boundary cuts. if (!!renderer_->r_drawCull->intVal) { - auto a = AabbFromCmdQuad(c->quad, nextViewport_); - auto b = AabbFromViewport(nextViewport_); - bool intersects = AabbAabbIntersects(a, b); - if (!intersects) { + const auto minX = *std::min_element(std::begin(c->quad.x), std::end(c->quad.x)); + const auto maxX = *std::max_element(std::begin(c->quad.x), std::end(c->quad.x)); + const auto minY = *std::min_element(std::begin(c->quad.y), std::end(c->quad.y)); + const auto maxY = *std::max_element(std::begin(c->quad.y), std::end(c->quad.y)); + const bool intersects = minX < vp.extent.x && maxX > 0.0f && minY < vp.extent.y && maxY > 0.0f; + if (!intersects) break; - } } // If the current batch is incompatible key-wise, dispatch it to get a fresh @@ -538,22 +438,16 @@ struct AdjacentMergeStrategy : RenderStrategy { Vertex quad[4]{}; for (int v = 0; v < 4; v++) { auto& q = quad[v]; - auto& vp = nextViewport_; q.u = c->quad.s[v]; q.v = c->quad.t[v]; - q.x = c->quad.x[v]; - q.y = c->quad.y[v]; + q.x = c->quad.x[v] + vp.lo.x; + q.y = c->quad.y[v] + vp.lo.y; q.r = tint_[0]; q.g = tint_[1]; q.b = tint_[2]; q.a = tint_[3]; - q.viewX = (float)vp.x; - q.viewY = (float)vp.y; - q.viewW = (float)vp.width; - q.viewH = (float)vp.height; q.texId = (float)texSlot; q.stackIdx = (float)c->quad.stackLayer; - q.maskIdx = (float)c->quad.maskLayer; } // 3-2 // |/| @@ -586,6 +480,7 @@ struct AdjacentMergeStrategy : RenderStrategy { size_t vertexCount = batch.vertices.size(); glBufferData(GL_ARRAY_BUFFER, vertexCount * sizeof(Vertex), nullptr, GL_STREAM_DRAW); glUseProgram(prog_); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto& key = batch_.key; auto& lastKey = lastDispatchKey_; @@ -595,31 +490,19 @@ struct AdjacentMergeStrategy : RenderStrategy { ImGui::Text("%d verts", batch.vertices.size()); } + int virtualW = renderer_->VirtualScreenWidth(); + int virtualH = renderer_->VirtualScreenHeight(); { - auto& vid = renderer_->sys->video->vid; - float fbScaleX = vid.fbSize[0] / (float)vid.size[0]; - float fbScaleY = vid.fbSize[1] / (float)vid.size[1]; - int virtualW = renderer_->VirtualScreenWidth(); - int virtualH = renderer_->VirtualScreenHeight(); glViewport(0, 0, virtualW, virtualH); - Mat4 mvpMatrix = OrthoMatrix(0, virtualW, virtualH, 0, -9999, 9999); - glUniformMatrix4fv(mvpMatrixLoc_, 1, GL_FALSE, mvpMatrix.data()); + glm::vec2 uScreenSize(virtualW, virtualH); + glUniform2fv(uScreenSizeLoc_, 1, glm::value_ptr(uScreenSize)); } - if (!lastKey || lastKey->blendMode != key.blendMode) { + if (!lastKey || lastKey->viewport != key.viewport) { if (showStats_) { - ImGui::Text("New blend mode %s", s_blendModeString.at((r_blendMode_e)key.blendMode)); - } - switch (key.blendMode) { - case RB_ALPHA: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case RB_PRE_ALPHA: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - break; - case RB_ADDITIVE: - glBlendFunc(GL_ONE, GL_ONE); - break; + ImGui::Text("New viewport at %d,%d with size %dx%d", key.viewport.lo.x, key.viewport.lo.y, key.viewport.extent.x, key.viewport.extent.y); } + const GLint scissorY = virtualH - key.viewport.extent.y - key.viewport.lo.y; + glScissor(key.viewport.lo.x, scissorY, key.viewport.extent.x, key.viewport.extent.y); } { for (size_t i = 0, numTex = texLocs_.size(); i < numTex; ++i) { @@ -657,7 +540,7 @@ struct AdjacentMergeStrategy : RenderStrategy { r_renderer_c* renderer_{}; GLuint prog_{}; std::vector texLocs_; - GLint mvpMatrixLoc_{}; + GLint uScreenSizeLoc_{}; size_t batchTextureCap_{}; GLuint vbo_{}; @@ -673,7 +556,6 @@ struct AdjacentMergeStrategy : RenderStrategy { }; BatchKey latchKey_{}; - r_viewport_s nextViewport_{}; r_tex_c* nextTex_{}; std::optional lastDispatchKey_; TexturedBatch batch_; @@ -920,8 +802,6 @@ void r_renderer_c::Init(r_featureFlag_e features) } fmt::format_to(fmt::appender(buf), R"( {{ color = texture(s_tex[{}], vec3(v_texcoord, v_texId.y)); - if (v_texId.z > -0.5) - color *= texture(s_tex[{}], vec3(v_texcoord, v_texId.z)); }} )", i, i); } @@ -1140,7 +1020,6 @@ void r_renderer_c::BeginFrame() curLayer = layerList[0]; SetViewport(); - SetBlendMode(RB_ALPHA); DrawColor(); beginFrameToc = std::chrono::steady_clock::now(); @@ -1597,7 +1476,6 @@ void r_renderer_c::SetDrawLayer(int layer, int subLayer) } curLayer = newCurLayer; curLayer->SetViewport(&curViewport); - curLayer->SetBlendMode(curBlendMode); } void r_renderer_c::SetDrawSubLayer(int subLayer) @@ -1617,19 +1495,11 @@ void r_renderer_c::SetViewport(int x, int y, int width, int height) width = VirtualScreenWidth(); height = VirtualScreenHeight(); } - curViewport.x = x; - curViewport.y = y; - curViewport.width = width; - curViewport.height = height; + curViewport.lo = glm::ivec2(x, y); + curViewport.extent = glm::ivec2(width, height); curLayer->SetViewport(&curViewport); } -void r_renderer_c::SetBlendMode(int mode) -{ - curBlendMode = mode; - curLayer->SetBlendMode(mode); -} - void r_renderer_c::DrawColor(const col4_t col) { if (col) { @@ -1659,7 +1529,7 @@ void r_renderer_c::GetDrawColor(col4_t color) color[3] = drawColor[3]; } -void r_renderer_c::DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1, glm::vec2 uv2, int stackLayer, std::optional maskLayer) +void r_renderer_c::DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1, glm::vec2 uv2, int stackLayer) { DrawImageQuad(hnd, pos, @@ -1670,10 +1540,10 @@ void r_renderer_c::DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent { uv2.s, uv1.t }, uv2, { uv1.s, uv2.t }, - stackLayer, maskLayer); + stackLayer); } -void r_renderer_c::DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0, glm::vec2 uv1, glm::vec2 uv2, glm::vec2 uv3, int stackLayer, std::optional maskLayer) +void r_renderer_c::DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0, glm::vec2 uv1, glm::vec2 uv2, glm::vec2 uv3, int stackLayer) { if (hnd) { curLayer->Bind(hnd->sh->tex); @@ -1689,7 +1559,7 @@ void r_renderer_c::DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, uv1.s, uv1.t, p1.x, p1.y, uv2.s, uv2.t, p2.x, p2.y, uv3.s, uv3.t, p3.x, p3.y, - stackLayer, maskLayer.value_or(-1)); + stackLayer); } void r_renderer_c::DrawString(float x, float y, int align, int height, const col4_t col, int font, const char* str) diff --git a/engine/render/r_main.h b/engine/render/r_main.h index 876a50ca..04a6b345 100644 --- a/engine/render/r_main.h +++ b/engine/render/r_main.h @@ -23,10 +23,21 @@ // Render viewport struct r_viewport_s { - int x; - int y; - int width; - int height; + glm::ivec2 lo; + glm::ivec2 extent; + + bool operator < (r_viewport_s const& rhs) const { + return std::tie(lo.x, lo.y, extent.x, extent.y) + < std::tie(rhs.lo.x, rhs.lo.y, rhs.extent.x, rhs.extent.y); + } + + bool operator == (r_viewport_s const& rhs) const { + return !(*this < rhs) && !(rhs < *this); + } + + bool operator != (r_viewport_s const& rhs) const { + return !(*this == rhs); + } }; // Render layer @@ -43,10 +54,9 @@ class r_layer_c { ~r_layer_c(); void SetViewport(r_viewport_s* viewport); - void SetBlendMode(int mode); void Bind(r_tex_c* tex); void Color(col4_t col); - void Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer = 0, int maskLayer = -1); + void Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer = 0); bool Render(); void Discard(); @@ -87,12 +97,11 @@ class r_renderer_c: public r_IRenderer, public conCmdHandler_c { void SetDrawSubLayer(int subLayer); int GetDrawLayer(); void SetViewport(int x = 0, int y = 0, int width = 0, int height = 0); - void SetBlendMode(int mode); void DrawColor(const col4_t col = NULL); void DrawColor(dword col); void GetDrawColor(col4_t color); - void DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1 = { 0, 0 }, glm::vec2 uv2 = { 1, 1 }, int stackLayer = 0, std::optional maskLayer = {}); - void DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0 = { 0, 0 }, glm::vec2 uv1 = { 1, 0 }, glm::vec2 uv2 = { 1, 1 }, glm::vec2 uv3 = { 0, 1 }, int stackLayer = 0, std::optional maskLayer = {}); + void DrawImage(r_shaderHnd_c* hnd, glm::vec2 pos, glm::vec2 extent, glm::vec2 uv1 = { 0, 0 }, glm::vec2 uv2 = { 1, 1 }, int stackLayer = 0 ); + void DrawImageQuad(r_shaderHnd_c* hnd, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, glm::vec2 p3, glm::vec2 uv0 = { 0, 0 }, glm::vec2 uv1 = { 1, 0 }, glm::vec2 uv2 = { 1, 1 }, glm::vec2 uv3 = { 0, 1 }, int stackLayer = 0); void DrawString(float x, float y, int align, int height, const col4_t col, int font, const char* str); void DrawStringFormat(float x, float y, int align, int height, const col4_t col, int font, const char* fmt, ...); int DrawStringWidth(int height, int font, const char* str); @@ -148,8 +157,7 @@ class r_renderer_c: public r_IRenderer, public conCmdHandler_c { col4_t drawColor = {}; // Current draw color - r_viewport_s curViewport; // Current viewport - int curBlendMode = 0; // Current blend mode + r_viewport_s curViewport; // Current viewport int numShader = 0; class r_shader_c *shaderList[R_MAXSHADERS] = {}; diff --git a/ui_api.cpp b/ui_api.cpp index 5ca7a322..73f40fc0 100644 --- a/ui_api.cpp +++ b/ui_api.cpp @@ -60,8 +60,8 @@ ** GetDrawLayer() ** SetViewport([x, y, width, height]) ** SetDrawColor(red, green, blue[, alpha]) / SetDrawColor("") -** DrawImage({imgHandle|nil}, left, top, width, height[, tcLeft, tcTop, tcRight, tcBottom][, stackIdx[, maskIdx]]) maskIdx: use a stack layer as multiplicative mask -** DrawImageQuad({imgHandle|nil}, x1, y1, x2, y2, x3, y3, x4, y4[, s1, t1, s2, t2, s3, t3, s4, t4][, stackIdx[, maskIdx]]) +** DrawImage({imgHandle|nil}, left, top, width, height[, tcLeft, tcTop, tcRight, tcBottom][, stackIdx]) +** DrawImageQuad({imgHandle|nil}, x1, y1, x2, y2, x3, y3, x4, y4[, s1, t1, s2, t2, s3, t3, s4, t4][, stackIdx]) ** DrawString(left, top, align{"LEFT"|"CENTER"|"RIGHT"|"CENTER_X"|"RIGHT_X"}, height, font{"FIXED"|"VAR"|"VAR BOLD"|"FONTIN SC"|"FONTIN SC ITALIC"|"FONTIN"|"FONTIN ITALIC"}, "") ** width = DrawStringWidth(height, font{"FIXED"|"VAR"|"VAR BOLD"|"FONTIN SC"|"FONTIN SC ITALIC"|"FONTIN"|"FONTIN ITALIC"}, "") ** index = DrawStringCursorIndex(height, font{"FIXED"|"VAR"|"VAR BOLD"|"FONTIN SC"|"FONTIN SC ITALIC"|"FONTIN"|"FONTIN ITALIC"}, "", cursorX, cursorY) @@ -813,18 +813,6 @@ static int l_SetViewport(lua_State* L) return 0; } -static int l_SetBlendMode(lua_State* L) -{ - ui_main_c* ui = GetUIPtr(L); - ui->LAssert(L, ui->renderer != NULL, "Renderer is not initialised"); - ui->LAssert(L, ui->renderEnable, "SetViewport() called outside of OnFrame"); - int n = lua_gettop(L); - ui->LAssert(L, n >= 1, "Usage: SetBlendMode(mode)"); - static const char* modeMap[6] = { "ALPHA", "PREALPHA", "ADDITIVE", NULL }; - ui->renderer->SetBlendMode(luaL_checkoption(L, 1, "ALPHA", modeMap)); - return 0; -} - static int l_SetDrawColor(lua_State* L) { ui_main_c* ui = GetUIPtr(L); @@ -894,7 +882,7 @@ static int l_DrawImage(lua_State* L) ui->LAssert(L, ui->renderer != NULL, "Renderer is not initialised"); ui->LAssert(L, ui->renderEnable, "DrawImage() called outside of OnFrame"); int n = lua_gettop(L); - const char* usage = "Usage: DrawImage({imgHandle|nil}, left, top, width, height[, tcLeft, tcTop, tcRight, tcBottom][, stackIdx[, mask]])"; + const char* usage = "Usage: DrawImage({imgHandle|nil}, left, top, width, height[, tcLeft, tcTop, tcRight, tcBottom][, stackIdx])"; ui->LAssert(L, n >= 5, usage); if (!lua_isnil(L, 1) && !ui->IsUserData(L, 1, "uiimghandlemeta")) { @@ -904,23 +892,18 @@ static int l_DrawImage(lua_State* L) r_shaderHnd_c* hnd = NULL; glm::vec2 xys[2]{}, uvs[2]{}; int stackLayer = 0; - std::optional maskLayer{}; - // | n |img| corners | uvs | stack | mask | - // | 5 | X | X | | | | - // | 6 | X | X | | X | | - // | 7 | X | X | | X | X | - // | 9 | X | X | X | | | - // | 10 | X | X | X | X | | - // | 11 | X | X | X | X | X | + // | n |img| corners | uvs | stack | + // | 5 | X | X | | | + // | 6 | X | X | | X | + // | 9 | X | X | X | | + // | 10 | X | X | X | X | - enum ArgFlag : uint8_t { AF_IMG = 0x1, AF_XY = 0x2, AF_UV = 0x4, AF_STACK = 0x8, AF_MASK = 0x10 }; + enum ArgFlag : uint8_t { AF_IMG = 0x1, AF_XY = 0x2, AF_UV = 0x4, AF_STACK = 0x8 }; ArgFlag af{}; switch (n) { - case 11: af = (ArgFlag)(af | AF_MASK); case 10: af = (ArgFlag)(af | AF_STACK); case 9: af = (ArgFlag)(af | AF_IMG | AF_XY | AF_UV); break; - case 7: af = (ArgFlag)(af | AF_MASK); case 6: af = (ArgFlag)(af | AF_STACK); case 5: af = (ArgFlag)(af | AF_IMG | AF_XY); break; default: ui->LAssert(L, false, usage); @@ -987,23 +970,7 @@ static int l_DrawImage(lua_State* L) k += 1; } - if (af & AF_MASK) { - if (!lua_isnil(L, k)) { - int isInt; - int val = (int)lua_tointegerx(L, k, &isInt); - - if (!isInt) { - ui->LAssert(L, false, "DrawImage() argument %d: expected integer or nil, got %s", k, luaL_typename(L, k)); - } - ui->LAssert(L, val > 0, "DrawImage() argument %d: expected positive integer, got %d", k, val); - if (maxStackValue.has_value()) - ui->LAssert(L, val <= *maxStackValue, "DrawImage() argument %d: expected valid stack index <= %d, got %d", k, *maxStackValue, val); - maskLayer = val - 1; - } - k += 1; - } - - ui->renderer->DrawImage(hnd, xys[0], xys[1], uvs[0], uvs[1], stackLayer, maskLayer); + ui->renderer->DrawImage(hnd, xys[0], xys[1], uvs[0], uvs[1], stackLayer); return 0; } @@ -1014,7 +981,7 @@ static int l_DrawImageQuad(lua_State* L) ui->LAssert(L, ui->renderer != NULL, "Renderer is not initialised"); ui->LAssert(L, ui->renderEnable, "DrawImageQuad() called outside of OnFrame"); int n = lua_gettop(L); - const char* usage = "Usage: DrawImageQuad({imgHandle|nil}, x1, y1, x2, y2, x3, y3, x4, y4[, s1, t1, s2, t2, s3, t3, s4, t4][, stackIdx[, mask]])"; + const char* usage = "Usage: DrawImageQuad({imgHandle|nil}, x1, y1, x2, y2, x3, y3, x4, y4[, s1, t1, s2, t2, s3, t3, s4, t4][, stackIdx])"; ui->LAssert(L, n >= 9, usage); if (!lua_isnil(L, 1) && ! ui->IsUserData(L, 1, "uiimghandlemeta")) { ui->LAssert(L, false, "DrawImageQuad() argument 1: expected image handle or nil, got %s", luaL_typename(L, 1)); @@ -1023,23 +990,18 @@ static int l_DrawImageQuad(lua_State* L) r_shaderHnd_c* hnd = NULL; glm::vec2 xys[4]{}, uvs[4]{}; int stackLayer = 0; - std::optional maskLayer{}; - // | n |img| corners | uvs | stack | mask | - // | 9 | X | X | | | | - // | 10 | X | X | | X | | - // | 11 | X | X | | X | X | - // | 17 | X | X | X | | | - // | 18 | X | X | X | X | | - // | 19 | X | X | X | X | X | - - enum ArgFlag : uint8_t { AF_IMG = 0x1, AF_XY = 0x2, AF_UV = 0x4, AF_STACK = 0x8, AF_MASK = 0x10 }; + // | n |img| corners | uvs | stack | + // | 9 | X | X | | | + // | 10 | X | X | | X | + // | 17 | X | X | X | | + // | 18 | X | X | X | X | + + enum ArgFlag : uint8_t { AF_IMG = 0x1, AF_XY = 0x2, AF_UV = 0x4, AF_STACK = 0x8 }; ArgFlag af{}; switch (n) { - case 19: af = (ArgFlag)(af | AF_MASK); case 18: af = (ArgFlag)(af | AF_STACK); case 17: af = (ArgFlag)(af | AF_IMG | AF_XY | AF_UV); break; - case 11: af = (ArgFlag)(af | AF_MASK); case 10: af = (ArgFlag)(af | AF_STACK); case 9: af = (ArgFlag)(af | AF_IMG | AF_XY); break; default: ui->LAssert(L, false, usage); @@ -1108,25 +1070,7 @@ static int l_DrawImageQuad(lua_State* L) k += 1; } - if (af & AF_MASK) { - if (!lua_isnil(L, k)) { - int isInt; - const int val = (int)lua_tointegerx(L, k, &isInt); - - if (!isInt) { - ui->LAssert(L, false, "DrawImageQuad() argument %d: expected integer or nil, got %s", k, luaL_typename(L, k)); - } - ui->LAssert(L, val > 0, "DrawImageQuad() argument %d: expected positive integer, got %d", k, val); - if (maxStackValue.has_value()) - ui->LAssert(L, val <= *maxStackValue, "DrawImageQuad() argument %d: expected valid stack index <= %d, got %d", k, *maxStackValue, val); - - maskLayer = val - 1; - } - k += 1; - } - - - ui->renderer->DrawImageQuad(hnd, xys[0], xys[1], xys[2], xys[3], uvs[0], uvs[1], uvs[2], uvs[3], stackLayer, maskLayer); + ui->renderer->DrawImageQuad(hnd, xys[0], xys[1], xys[2], xys[3], uvs[0], uvs[1], uvs[2], uvs[3], stackLayer); return 0; } @@ -2274,7 +2218,6 @@ int ui_main_c::InitAPI(lua_State* L) ADDFUNC(SetDrawLayer); ADDFUNC(GetDrawLayer); ADDFUNC(SetViewport); - ADDFUNC(SetBlendMode); ADDFUNC(SetDrawColor); ADDFUNC(GetDrawColor); ADDFUNC(SetDPIScaleOverridePercent);