diff --git a/examples/sample-scenes/include/MouseCollisionScene.h b/examples/sample-scenes/include/MouseCollisionScene.h index 7ca3e25..2f900df 100644 --- a/examples/sample-scenes/include/MouseCollisionScene.h +++ b/examples/sample-scenes/include/MouseCollisionScene.h @@ -20,7 +20,7 @@ class MouseCollisionScene : public Scene m_debugInput = true; m_debugFly = true; - for (size_t i = 0; i < 600; i++) + for (size_t i = 0; i < 9900; i++) { float y = (int)(i / 20); diff --git a/include/weird-engine/Profiler.h b/include/weird-engine/Profiler.h index a2871a5..e104fde 100644 --- a/include/weird-engine/Profiler.h +++ b/include/weird-engine/Profiler.h @@ -137,8 +137,18 @@ namespace WeirdEngine << std::setw(10) << "% Frame" << "\n"; std::cout << "-----------------------------------------------------------------\n"; - for (const auto& stat : m_stats) + printStatsRange(0, m_stats.size(), totalFrameTimeMs); + + std::cout << "=================================================================\n\n"; + } + + void printStatsRange(size_t start, size_t end, double totalFrameTimeMs) + { + size_t i = start; + while (i < end) { + const auto& stat = m_stats[i]; + std::string indent(stat.depth * 4, ' '); std::string name = indent + stat.name; double avgTimeMs = stat.totalTimeMs / stat.count; @@ -150,8 +160,41 @@ namespace WeirdEngine std::cout << std::left << std::setw(40) << name << std::right << std::setw(15) << std::fixed << std::setprecision(3) << avgTimeMs << std::setw(9) << std::fixed << std::setprecision(1) << percentage << "%\n"; + + // Find end of this stat's subtree + size_t subEnd = i + 1; + while (subEnd < end && m_stats[subEnd].depth > stat.depth) + subEnd++; + + if (subEnd > i + 1) + { + // Sum direct children total times + double childrenTotalMs = 0.0; + for (size_t j = i + 1; j < subEnd; j++) + { + if (m_stats[j].depth == stat.depth + 1) + childrenTotalMs += m_stats[j].totalTimeMs; + } + + // Print children recursively + printStatsRange(i + 1, subEnd, totalFrameTimeMs); + + // If unaccounted time exceeds 1% of frame, print an "Other" row + double unaccountedMs = stat.totalTimeMs - childrenTotalMs; + double unaccountedPct = totalFrameTimeMs > 0.0 ? (unaccountedMs / totalFrameTimeMs) * 100.0 : 0.0; + if (unaccountedPct > 1.0) + { + std::string otherIndent((stat.depth + 1) * 4, ' '); + std::string otherName = otherIndent + "Other"; + double otherAvgMs = unaccountedMs / stat.count; + std::cout << std::left << std::setw(40) << otherName << std::right << std::setw(15) + << std::fixed << std::setprecision(3) << otherAvgMs << std::setw(9) << std::fixed + << std::setprecision(1) << unaccountedPct << "%\n"; + } + } + + i = subEnd; } - std::cout << "=================================================================\n\n"; } struct StackItem diff --git a/include/weird-engine/systems/2DSDFShaderGenerationSystem.h b/include/weird-engine/systems/2DSDFShaderGenerationSystem.h index 2a67f69..573c66c 100644 --- a/include/weird-engine/systems/2DSDFShaderGenerationSystem.h +++ b/include/weird-engine/systems/2DSDFShaderGenerationSystem.h @@ -107,8 +107,8 @@ namespace WeirdEngine::SDFShaderGenerationSystem2D oss << "int idx = dataOffset + " << 2 * i << ";\n"; // Fetch parameters - oss << "vec4 parameters0 = texelFetch(t_shapeBuffer, ivec2(idx, 0), 0);\n"; - oss << "vec4 parameters1 = texelFetch(t_shapeBuffer, ivec2(idx + 1, 0), 0);\n"; + oss << "vec4 parameters0 = texelFetch(t_shapeBuffer, ivec2(idx % 16384, idx / 16384), 0);\n"; + oss << "vec4 parameters1 = texelFetch(t_shapeBuffer, ivec2((idx + 1) % 16384, (idx + 1) / 16384), 0);\n"; // Get distance function auto fragmentCode = sdfs[shape.distanceFieldId]->print(); diff --git a/include/weird-renderer/core/SDF2DRenderPipeline.h b/include/weird-renderer/core/SDF2DRenderPipeline.h index 9b3789b..c8e6e1f 100644 --- a/include/weird-renderer/core/SDF2DRenderPipeline.h +++ b/include/weird-renderer/core/SDF2DRenderPipeline.h @@ -7,6 +7,7 @@ #include "weird-renderer/resources/Shader.h" #include "weird-renderer/resources/Texture.h" #include "weird-renderer/scene/Camera.h" +#include namespace WeirdEngine { @@ -36,6 +37,7 @@ namespace WeirdEngine float motionBlurBlendSpeed; bool debugDistanceField; bool debugMaterialColors; + bool debugGrid = false; float ballK; float ambienOcclusionRadius; float ambienOcclusionStrength; @@ -108,6 +110,16 @@ namespace WeirdEngine RenderTarget m_litSceneRender; DataBuffer m_shapeDataBuffer; + DataBuffer m_gridHeaderBuffer; + DataBuffer m_gridIndicesBuffer; + + // Reusable scratch buffers for grid building — allocated once, reused every frame + struct ObjBounds { int minX, minY, maxX, maxY; }; + std::vector m_gridObjBounds; + std::vector m_gridCellCounts; + std::vector m_gridCellOffsets; + std::vector m_gridHeader; + std::vector m_gridIndices; glm::mat4 m_oldCameraMatrix; glm::mat4 @@ -116,9 +128,10 @@ namespace WeirdEngine bool horizontal = true; glm::vec3 cameraPositionChange; - - void renderDistanceField(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, const Camera& camera, - double time, double delta); + struct GridInfo { float minX, minY, stepX, stepY; int gridCols, gridRows, indexTexWidth, indexTexHeight; }; + GridInfo buildAccelerationGrid(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, + const Camera& camera); void renderDistanceField(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, const Camera& camera, + double time, double delta); void applyJumpFloodCorrection(double time); void upscaleDistance(); void renderMaterialColors(const Camera& camera, double time, double delta); diff --git a/include/weird-renderer/resources/DataBuffer.h b/include/weird-renderer/resources/DataBuffer.h index 77b2823..8120822 100644 --- a/include/weird-renderer/resources/DataBuffer.h +++ b/include/weird-renderer/resources/DataBuffer.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include #include namespace WeirdEngine @@ -40,14 +42,43 @@ namespace WeirdEngine if (byteSize == 0 || (byteSize % texelBytes != 0)) return; - GLsizei width = static_cast(byteSize / texelBytes); + GLint maxTexSize = 16384; + // glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); + + size_t totalTexels = byteSize / texelBytes; + GLsizei texW, texH; + + auto s = static_cast(totalTexels); + if (s <= maxTexSize) + { + texW = static_cast(totalTexels); + texH = 1; + } + else + { + // Wrap into 2D: use maxTexSize as row width, add enough rows + texW = static_cast(maxTexSize); + texH = static_cast((totalTexels + maxTexSize - 1) / maxTexSize); + } + + // When wrapping to 2D, texW*texH may exceed totalTexels (due to ceiling + // division). Pad with zeros so OpenGL never reads past the source buffer. + const void* uploadPtr = data; + std::vector paddedData; + size_t paddedTexels = static_cast(texW) * static_cast(texH); + if (paddedTexels > totalTexels) + { + paddedData.resize(paddedTexels * 4, 0.0f); // 4 floats per RGBA32F texel + std::memcpy(paddedData.data(), data, byteSize); + uploadPtr = paddedData.data(); + } GLint previousActiveTexture = 0; GLint previousTextureBinding2D = 0; glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture); glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTextureBinding2D); glBindTexture(GL_TEXTURE_2D, m_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, 1, 0, GL_RGBA, GL_FLOAT, data); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, texW, texH, 0, GL_RGBA, GL_FLOAT, uploadPtr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -61,6 +92,27 @@ namespace WeirdEngine uploadRawData(data, count * sizeof(T)); } + template void uploadData2D(const T* data, size_t width, size_t height) const + { + size_t byteSize = width * height * sizeof(T); + const size_t texelBytes = 4 * sizeof(float); // one RGBA32F texel + if (byteSize == 0 || (byteSize % texelBytes != 0)) + return; + + GLint previousActiveTexture = 0; + GLint previousTextureBinding2D = 0; + glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousTextureBinding2D); + glBindTexture(GL_TEXTURE_2D, m_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, static_cast(width), static_cast(height), 0, GL_RGBA, GL_FLOAT, data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, static_cast(previousTextureBinding2D)); + glActiveTexture(static_cast(previousActiveTexture)); + } + GLuint getTexture() const { return m_texture; diff --git a/include/weird-renderer/resources/Shader.h b/include/weird-renderer/resources/Shader.h index b3e80b0..3015816 100644 --- a/include/weird-renderer/resources/Shader.h +++ b/include/weird-renderer/resources/Shader.h @@ -54,6 +54,11 @@ namespace WeirdEngine glUniform1i(getUniformLocation(name), value); } + void setUniform(const std::string& name, const glm::ivec2& value) const + { + glUniform2iv(getUniformLocation(name), 1, &value[0]); + } + void setUniform(const std::string& name, const glm::vec2& value) const { glUniform2fv(getUniformLocation(name), 1, &value[0]); diff --git a/src/weird-renderer/core/Renderer.cpp b/src/weird-renderer/core/Renderer.cpp index dffcc9c..3017f86 100644 --- a/src/weird-renderer/core/Renderer.cpp +++ b/src/weird-renderer/core/Renderer.cpp @@ -121,6 +121,7 @@ namespace WeirdEngine auto& result = renderScene(scene, time, clampedDelta); output(scene, result, clampedDelta); + glFinish(); static bool showDebugUI = false; @@ -302,9 +303,11 @@ namespace WeirdEngine bool enable3DSDFs = true; - // Render geometry + // 3D if (enable3D) { + PROFILE_SCOPE("3D Render"); + auto& renderQueue = scene.getDrawQueue(); // TODO: sort and then draw it // Set up framebuffer for 3D scene rendering @@ -359,6 +362,7 @@ namespace WeirdEngine // Draw the render plane with ray marching shader m_renderPlane.draw(m_3DsdfShaderProgram); + glFinish(); } // Render 3D geometry objects (with depth writing) @@ -376,6 +380,8 @@ namespace WeirdEngine // Draw objects in the scene (3D models) scene.renderModels(m_3DSceneRender, m_geometryShaderProgram, m_instancedGeometryShaderProgram); } + + glFinish(); if (!enable2D) { diff --git a/src/weird-renderer/core/SDF2DRenderPipeline.cpp b/src/weird-renderer/core/SDF2DRenderPipeline.cpp index 6417f33..ca1bf0f 100644 --- a/src/weird-renderer/core/SDF2DRenderPipeline.cpp +++ b/src/weird-renderer/core/SDF2DRenderPipeline.cpp @@ -5,6 +5,7 @@ #include #include "weird-engine/vec.h" +#include "weird-engine/Profiler.h" namespace WeirdEngine { @@ -293,46 +294,233 @@ namespace WeirdEngine renderBackground(camera, time); applyLighting(camera, time, backgroundTexture); + glFinish(); // Makes sense for profiler + return m_litSceneTexture; } + SDF2DRenderPipeline::GridInfo SDF2DRenderPipeline::buildAccelerationGrid(vec4* shapeData, uint32_t dataSize, + uint32_t shapeCount, + const Camera& camera) + { + PROFILE_SCOPE(m_config.isUI ? "Build grid (UI)" : "Build grid (World)"); + + // Build acceleration grid using CPU padding spatial partitioning. + // Grid bounds are derived entirely from the camera frustum, so off-screen circles are + // never inserted. This relies on the 2D camera convention where: + // zoom = -view[3].z (positive distance from origin) + // camXY = view[3].xy (camera translation in world space) + // If the camera representation changes, update the frustum derivation below accordingly. + constexpr int indexTexWidth = 1024; + + int objectCount = static_cast(dataSize) - (2 * static_cast(shapeCount)); + + float objectRadius = m_config.isUI ? 5.0f : 1.0f; + float cellPad = m_config.ballK + objectRadius; + + // Derive the visible world-space rectangle from the camera matrix + float zoom = -camera.view[3].z; + float camX = camera.view[3].x; + float camY = camera.view[3].y; + float aspectRatio = static_cast(m_distanceSampleWidth) / static_cast(m_distanceSampleHeight); + float overscan = std::clamp(m_config.distanceOverscan, 0.0f, 0.5f); + + float uvXMin, uvYMin, uvXMax, uvYMax; + if (m_config.isUI) + { + uvXMin = 0.0f; + uvYMin = 0.0f; + uvXMax = 1.0f * aspectRatio; + uvYMax = 1.0f; + } + else + { + uvXMin = (-1.0f - overscan) * aspectRatio; + uvYMin = (-1.0f - overscan); + uvXMax = (1.0f + overscan) * aspectRatio; + uvYMax = (1.0f + overscan); + } + + float viewMinX = zoom * uvXMin - camX; + float viewMinY = zoom * uvYMin - camY; + float viewMaxX = zoom * uvXMax - camX; + float viewMaxY = zoom * uvYMax - camY; + if (viewMinX > viewMaxX) + std::swap(viewMinX, viewMaxX); + if (viewMinY > viewMaxY) + std::swap(viewMinY, viewMaxY); + + // Expand frustum by cellPad so circles that straddle the view edge are included + float minX = viewMinX - cellPad; + float minY = viewMinY - cellPad; + float maxX = viewMaxX + cellPad; + float maxY = viewMaxY + cellPad; + + // Cell size is the object diameter so a circle fits in ~1 cell (2x2 worst-case). + // ballK only affects the insertion radius, not the cell size. + float idealCellSize = 2.0f * objectRadius; + if (idealCellSize < 0.5f) + idealCellSize = 0.5f; + int gridCols = static_cast(std::ceil((maxX - minX) / idealCellSize)); + int gridRows = static_cast(std::ceil((maxY - minY) / idealCellSize)); + + if (gridCols < 1) + gridCols = 1; + if (gridRows < 1) + gridRows = 1; + if (gridCols > 2048) + gridCols = 2048; + if (gridRows > 2048) + gridRows = 2048; + + float stepX = (maxX - minX) / gridCols; + float stepY = (maxY - minY) / gridRows; + + int numCells = gridCols * gridRows; + + // Reuse member scratch buffers — no heap allocation unless the scene grows + m_gridObjBounds.clear(); + m_gridObjBounds.resize(objectCount); + m_gridCellCounts.assign(numCells, 0); + + // Single pass: compute per-object cell ranges and tally cell counts. + // Objects whose cell range is entirely outside [0, gridCols/Rows) are naturally skipped. + int visibleObjects = 0; + for (int i = 0; i < objectCount; ++i) + { + float cx = shapeData[i].x; + float cy = shapeData[i].y; + int minCellX = static_cast((cx - cellPad - minX) / stepX); + int minCellY = static_cast((cy - cellPad - minY) / stepY); + int maxCellX = static_cast((cx + cellPad - minX) / stepX); + int maxCellY = static_cast((cy + cellPad - minY) / stepY); + + // Cull objects entirely outside the frustum-aligned grid + if (maxCellX < 0 || maxCellY < 0 || minCellX >= gridCols || minCellY >= gridRows) + { + m_gridObjBounds[i] = {-1, -1, -1, -1}; + continue; + } + + minCellX = std::max(0, minCellX); + minCellY = std::max(0, minCellY); + maxCellX = std::min(gridCols - 1, maxCellX); + maxCellY = std::min(gridRows - 1, maxCellY); + + m_gridObjBounds[i] = {minCellX, minCellY, maxCellX, maxCellY}; + ++visibleObjects; + + for (int cyI = minCellY; cyI <= maxCellY; ++cyI) + for (int cxI = minCellX; cxI <= maxCellX; ++cxI) + m_gridCellCounts[cyI * gridCols + cxI]++; + } + + // Prefix sum to compute cell offsets + m_gridCellOffsets.resize(numCells); + int totalIndices = 0; + for (int i = 0; i < numCells; ++i) + { + m_gridCellOffsets[i] = totalIndices; + totalIndices += m_gridCellCounts[i]; + } + + // Pre-allocate index buffer at final padded size to avoid realloc during fill + int indexTexHeight = std::max(1, (totalIndices + indexTexWidth - 1) / indexTexWidth); + int paddedSize = indexTexWidth * indexTexHeight; + + m_gridHeader.resize(numCells); + m_gridIndices.assign(paddedSize, glm::vec4(0.0f)); + + // Fill index buffer using offset-as-write-cursor directly (no extra copy) + for (int i = 0; i < objectCount; ++i) + { + const ObjBounds& b = m_gridObjBounds[i]; + if (b.minX < 0) + continue; // culled + for (int cyI = b.minY; cyI <= b.maxY; ++cyI) + { + for (int cxI = b.minX; cxI <= b.maxX; ++cxI) + { + int cellIdx = cyI * gridCols + cxI; + m_gridIndices[m_gridCellOffsets[cellIdx]++] = + glm::vec4(static_cast(i), 0.0f, 0.0f, 0.0f); + } + } + } + + // Restore offsets for header (they were incremented above as write cursors) + int writePos = 0; + for (int i = 0; i < numCells; ++i) + { + m_gridHeader[i] = + glm::vec4(static_cast(writePos), static_cast(m_gridCellCounts[i]), 0.0f, 0.0f); + writePos += m_gridCellCounts[i]; + } + + return {minX, minY, stepX, stepY, gridCols, gridRows, indexTexWidth, indexTexHeight}; + } + void SDF2DRenderPipeline::renderDistanceField(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, const Camera& camera, double time, double delta) { - int previousDistanceIndex = m_distanceTextureDoubleBufferIdx; - m_distanceTextureDoubleBufferIdx = (m_distanceTextureDoubleBufferIdx + 1) % 2; - - m_distanceTextureDoubleBuffer[m_distanceTextureDoubleBufferIdx]->bind(); - m_distanceShader.use(); - - // Set uniforms - m_prevFrameCameraMatrix = - m_oldCameraMatrix; // save before overwrite so other shaders can access the true previous frame matrix - m_distanceShader.setUniform("u_camMatrix", camera.view); - m_distanceShader.setUniform("u_oldCamMatrix", m_oldCameraMatrix); - m_oldCameraMatrix = camera.view; - - cameraPositionChange = camera.position - m_lastCameraPosition; - m_lastCameraPosition = camera.position; - m_distanceShader.setUniform("u_camPositionChange", cameraPositionChange); - - m_distanceShader.setUniform("u_time", time); - m_distanceShader.setUniform("u_deltaTime", static_cast(delta)); - m_distanceShader.setUniform("u_resolution", glm::vec2(m_distanceSampleWidth, m_distanceSampleHeight)); - m_distanceShader.setUniform("u_overscan", std::clamp(m_config.distanceOverscan, 0.0f, 0.5f)); - m_distanceShader.setUniform("u_motionBlurBlendSpeed", m_config.motionBlurBlendSpeed); - m_distanceShader.setUniform("u_k", m_config.ballK); - - m_distanceShader.setUniform("t_colorTexture", 0); - m_distanceTextureDoubleBuffer[previousDistanceIndex]->getColorAttachment()->bind(0); - - m_distanceShader.setUniform("u_loadedObjects", (int)dataSize); - m_distanceShader.setUniform("u_customShapeCount", static_cast(shapeCount)); - m_shapeDataBuffer.uploadData(shapeData, dataSize); - m_distanceShader.setUniform("t_shapeBuffer", 1); - m_shapeDataBuffer.bind(1); - - m_renderPlane.draw(m_distanceShader); + { + PROFILE_SCOPE(m_config.isUI ? "Upload data (UI)" : "Upload data (World)"); + + int previousDistanceIndex = m_distanceTextureDoubleBufferIdx; + m_distanceTextureDoubleBufferIdx = (m_distanceTextureDoubleBufferIdx + 1) % 2; + + GridInfo grid = buildAccelerationGrid(shapeData, dataSize, shapeCount, camera); + m_gridHeaderBuffer.uploadData2D(m_gridHeader.data(), grid.gridCols, grid.gridRows); + m_gridIndicesBuffer.uploadData2D(m_gridIndices.data(), grid.indexTexWidth, + grid.indexTexHeight); + + m_distanceTextureDoubleBuffer[m_distanceTextureDoubleBufferIdx]->bind(); + m_distanceShader.use(); + + // Set uniforms + m_prevFrameCameraMatrix = m_oldCameraMatrix; // save before overwrite so other shaders can access the + // true previous frame matrix + m_distanceShader.setUniform("u_camMatrix", camera.view); + m_distanceShader.setUniform("u_oldCamMatrix", m_oldCameraMatrix); + m_oldCameraMatrix = camera.view; + + cameraPositionChange = camera.position - m_lastCameraPosition; + m_lastCameraPosition = camera.position; + m_distanceShader.setUniform("u_camPositionChange", cameraPositionChange); + + m_distanceShader.setUniform("u_time", time); + m_distanceShader.setUniform("u_deltaTime", static_cast(delta)); + m_distanceShader.setUniform("u_resolution", glm::vec2(m_distanceSampleWidth, m_distanceSampleHeight)); + m_distanceShader.setUniform("u_overscan", std::clamp(m_config.distanceOverscan, 0.0f, 0.5f)); + m_distanceShader.setUniform("u_motionBlurBlendSpeed", m_config.motionBlurBlendSpeed); + m_distanceShader.setUniform("u_k", m_config.ballK); + + m_distanceShader.setUniform("t_colorTexture", 0); + m_distanceTextureDoubleBuffer[previousDistanceIndex]->getColorAttachment()->bind(0); + + m_distanceShader.setUniform("u_loadedObjects", (int)dataSize); + m_distanceShader.setUniform("u_customShapeCount", static_cast(shapeCount)); + m_shapeDataBuffer.uploadData(shapeData, dataSize); + m_distanceShader.setUniform("t_shapeBuffer", 1); + m_shapeDataBuffer.bind(1); + + m_distanceShader.setUniform("u_gridBoundsMin", glm::vec2(grid.minX, grid.minY)); + m_distanceShader.setUniform("u_gridStep", glm::vec2(grid.stepX, grid.stepY)); + m_distanceShader.setUniform("u_gridCols", grid.gridCols); + m_distanceShader.setUniform("u_gridRows", grid.gridRows); + + m_distanceShader.setUniform("t_gridHeader", 2); + m_gridHeaderBuffer.bind(2); + m_distanceShader.setUniform("t_gridIndices", 3); + m_gridIndicesBuffer.bind(3); + } + + // Upload grid + { + PROFILE_SCOPE(m_config.isUI ? "Render distance (UI)" : "Render distance (World)"); + m_renderPlane.draw(m_distanceShader); + glFinish(); // Makes sense for profiler + } } void SDF2DRenderPipeline::applyJumpFloodCorrection(double time) @@ -579,6 +767,12 @@ namespace WeirdEngine else m_lightingShader.removeDefine("DEBUG_SHOW_COLORS"); } + if (ImGui::Checkbox("Show grid", &m_config.debugGrid)) + { + if (m_config.debugGrid) m_distanceShader.addDefine("DEBUG_SHOW_GRID"); + else m_distanceShader.removeDefine("DEBUG_SHOW_GRID"); + } + ImGui::SeparatorText("Ambient Occlusion"); ImGui::SliderFloat("AO Radius", &m_config.ambienOcclusionRadius, 0.0f, 20.0f); ImGui::SliderFloat("AO Strength", &m_config.ambienOcclusionStrength, 0.0f, 1.0f); diff --git a/src/weird-renderer/shaders/2d/lighting.frag b/src/weird-renderer/shaders/2d/lighting.frag index 7ba87e6..dda9cf1 100644 --- a/src/weird-renderer/shaders/2d/lighting.frag +++ b/src/weird-renderer/shaders/2d/lighting.frag @@ -190,8 +190,11 @@ void main() #ifdef ANTIALIASING - // Distance change over one pixel - float smoothing = 1.0 * fwidth(distance); + // Distance change over one pixel. + // Cap to ~1 pixel of distance in finalDistance space so fwidth discontinuities at + // empty grid cell boundaries don't widen the AA range and create false shape lines. + float maxSmoothing = 2.0 * overscanScale / u_resolution.y; + float smoothing = min(1.0 * fwidth(distance), maxSmoothing); // Convert the distance to a factor between 0.0 and 1.0 float shapeFactor = 1.0 - smoothstep(-smoothing, smoothing, distance); @@ -312,7 +315,7 @@ void main() #endif // float debugDistance = 10.0 * distance; - float debugDistance = 0.5 * texture(t_distanceCorrectedTexture, screenUV).x; + float debugDistance = 0.5 * texture(t_distanceSampledTexture, screenUV).x; float value = 0.5 * (cos(500.0 * debugDistance) + 1.0); // value = debugDistance * 10.; diff --git a/src/weird-renderer/shaders/2d/sdf_distance.frag b/src/weird-renderer/shaders/2d/sdf_distance.frag index a81eaaa..35300e3 100644 --- a/src/weird-renderer/shaders/2d/sdf_distance.frag +++ b/src/weird-renderer/shaders/2d/sdf_distance.frag @@ -7,6 +7,7 @@ precision highp sampler2D; // #define BLEND_SHAPES // #define MOTION_BLUR +// #define DEBUG_SHOW_GRID out vec4 FragColor; @@ -30,6 +31,13 @@ uniform sampler2D t_colorTexture; uniform int u_loadedObjects; uniform highp sampler2D t_shapeBuffer; +uniform highp sampler2D t_gridHeader; +uniform highp sampler2D t_gridIndices; + +uniform vec2 u_gridBoundsMin; +uniform vec2 u_gridStep; +uniform int u_gridCols; +uniform int u_gridRows; uniform vec2 u_resolution; uniform float u_overscan; @@ -87,7 +95,7 @@ float sdPolygon(in vec2[SEGMENTS] v, in vec2 p) return s * sqrt(d); } -vec3 getColor(vec2 p, vec2 uv) +vec3 getDistanceMaterialMask(vec2 p, vec2 uv) { float minDist = 100000.0; float minColorDist = minDist; @@ -109,40 +117,39 @@ vec3 getColor(vec2 p, vec2 uv) float inv_k = 1.0 / u_k; - for (int i = 0; i < u_loadedObjects - (2 * u_customShapeCount); i++) + ivec2 cellCoord = ivec2((p - u_gridBoundsMin) / u_gridStep); + cellCoord = clamp(cellCoord, ivec2(0), ivec2(u_gridCols - 1, u_gridRows - 1)); + + vec2 cellData = texelFetch(t_gridHeader, cellCoord, 0).xy; + int startIndex = int(cellData.x); + int count = int(cellData.y); + + for (int i = 0; i < count; i++) { - vec4 positionSizeMaterial = texelFetch(t_shapeBuffer, ivec2(i, 0), 0); + int flatIndex = startIndex + i; + ivec2 indexCoord = ivec2(flatIndex % 1024, flatIndex / 1024); + int objectIndex = int(texelFetch(t_gridIndices, indexCoord, 0).x); + + // Shape buffer wraps to 2D at 16384 texels wide (matches DataBuffer::uploadRawData on ES drivers) + vec4 positionSizeMaterial = texelFetch(t_shapeBuffer, ivec2(objectIndex % 16384, objectIndex / 16384), 0); int materialId = int(positionSizeMaterial.w); #ifdef UI_PIPELINE - float objectDist = shape_circle(p - positionSizeMaterial.xy, 5.0); + float objectDist = shape_circle(p - positionSizeMaterial.xy, 5.0); #else - float objectDist = shape_circle(p - positionSizeMaterial.xy); + float objectDist = shape_circle(p - positionSizeMaterial.xy); #endif - // Inside ball mask is set to 0 - mask = objectDist <= 0.0 ? -objectDist * 4.0 : mask; - // mask = objectDist <= 0 ? 1.0 : mask; + // Inside ball mask is set to 0 + mask = objectDist <= 0.0 ? -objectDist * 4.0 : mask; #ifdef BLEND_SHAPES - finalMaterialId = objectDist <= minColorDist ? materialId : finalMaterialId; - -#else - - finalMaterialId = objectDist <= minDist ? materialId : finalMaterialId; - -#endif - -#ifdef BLEND_SHAPES - minDist = fOpUnionSoft(objectDist, minDist, u_k, inv_k); minColorDist = min(minColorDist, objectDist); - #else - + finalMaterialId = objectDist <= minDist ? materialId : finalMaterialId; minDist = min(minDist, objectDist); - #endif } @@ -152,6 +159,10 @@ vec3 getColor(vec2 p, vec2 uv) minDist = min(minDist, 10.0); // Clamp max distance in UI mode #endif +#ifdef DEBUG_SHOW_GRID + finalMaterialId = count / 10; // Color cells based on how many objects they contain, for debugging purposes +#endif + return vec3(minDist, max(float(finalMaterialId), 0.0), mask); } @@ -207,7 +218,7 @@ void main() float zoom = -u_camMatrix[3].z; vec2 pos = (zoom * uv) - u_camMatrix[3].xy; - vec3 result = getColor(pos, v_texCoord); + vec3 result = getDistanceMaterialMask(pos, v_texCoord); float d = result.x; #ifndef UI_PIPELINE @@ -280,6 +291,26 @@ void main() // finalDistance = abs(finalDistance - pixelSize) - (pixelSize); #endif +#ifdef DEBUG_SHOW_GRID + vec2 localP = pos - u_gridBoundsMin; + bool inBounds = localP.x >= 0.0 && localP.y >= 0.0 && + localP.x < float(u_gridCols) * u_gridStep.x && + localP.y < float(u_gridRows) * u_gridStep.y; + if (inBounds) + { + vec2 fracLocal = mod(localP, u_gridStep); + vec2 distToLine = min(fracLocal, u_gridStep - fracLocal); + float gridDistWorld = min(distToLine.x, distToLine.y); + float gridDistNorm = (gridDistWorld / zoom) * (0.5 / aspectRatio); + float lineHalfWidth = 1.0 / min(u_resolution.x, u_resolution.y); + if (gridDistNorm < lineHalfWidth) + { + finalDistance = gridDistNorm - lineHalfWidth; + // material = 16.0 - material; // Highlight out-of-bounds areas with a different color + } + } +#endif + FragColor = vec4(finalDistance, material, mask, 0.0); // This has no visual effect (multiplying by zero), but it forces