[Software Renderer/Windows] Severe Rendering Bugs, Freezes, and Performance Regressions
Note: This issue was analyzed and compiled by an AI coding agent assisting a human developer. We performed a deep dive into the software renderer fallback path and identified several critical issues affecting gogpu and wgpu.
Environment
- OS: Microsoft Windows 10 Pro (10.0.19045)
- Hardware GPU: Intel(R) HD Graphics 4600 (Integrated GPU)
- Go Version: 1.26.4
- Framework Versions:
github.com/gogpu/ui v0.1.40
github.com/gogpu/gg v0.49.5
github.com/gogpu/gogpu v0.43.0
github.com/gogpu/wgpu v0.30.7
Description
When running gogpu/ui applications (like the hello or gallery examples) on Windows without hardware acceleration (falling back to the Software Renderer), the application exhibits severe bugs ranging from complete freezes to corrupted visuals and overlapping layouts.
While the framework's development has done a fantastic job implementing modern GPU features (MSAA, Compositor Clipping) for hardware backends, these features heavily penalize the pure-Go CPU fallback. Additionally, several core WebGPU features are simply stubbed out or implemented incorrectly in the hal/software backend.
Here is a detailed breakdown of the 6 root causes we identified during our debugging session:
1. Massive Performance Regression / "Freeze" (ADR-021 & MSAA)
Symptom: The application appears to completely freeze immediately upon startup.
Root Cause: A goroutine dump reveals the application isn't deadlocked; it is spinning at 100% CPU in hal/software/raster.Rasterize executing SPIR-V fragment shaders.
In gg v0.49.5, ADR-021 enforces full-screen renders (encodeSubmitSurfaceGrouped) and ignores DamageRects when MSAA or grouped rendering is active. For a pure CPU software renderer, calculating complex SDF shaders for an entire 800x900 window (potentially at 4x MSAA) every frame creates an insurmountable fill-rate bottleneck. It takes minutes per frame, masquerading as a hard freeze.
Proposed Fix: The engine should detect the Software adapter and explicitly disable MSAA and full-screen grouped rendering to force the blitOnly path with strict DamageRects.
2. Missing OS Paint Event Propagation (WM_PAINT)
Symptom: Even when rendering is fast enough, the window often remains completely black until a pointer event (mouse move/click) forces a wake-up.
Root Cause: gogpu v0.43.0 defaults to event-driven rendering (WithContinuousRender(false)). The Windows message loop in internal/platform/platform_windows.go intercepts WM_PAINT and validates the region, but never queues a redraw event. WaitEvents() remains blocked.
Proposed Fix: Introduce an EventPaint to the platform interface and trigger app.RequestRedraw() when handling WM_PAINT.
3. Missing DrawIndexed Support
Symptom: Text rendering is either completely missing or severely corrupted.
Root Cause: Modern text pipelines (like MSDF) use indexed quads. However, DrawIndexed in hal/software/command.go is an empty no-op. All text draw calls are silently dropped.
Proposed Fix: Implement executeDrawIndexed and index-aware vertex fetchers (fetchTrianglesWithIndices) to properly resolve attributes.
4. Ignored Dynamic Uniform Buffer Offsets
Symptom: Elements within lists (e.g., 1000 ListView items) fail to layout correctly and instead stack on top of each other at (0,0).
Root Cause: The software backend's SetBindGroup ignores dynamicOffsets. The rasterizer always reads uniform data starting at offset 0.
Proposed Fix: Update bufferBinding to retain base offsets, and add dynamicOffsets logic inside buildExecutionContext to slice the buffer correctly before executing the shader.
5. Red/Blue (R/B) Channel Swap on Existing Pixels
Symptom: Backgrounds and borders turn orange instead of blue; colors degrade continuously on every redraw.
Root Cause: Windows offscreen DIBs use BGRA8Unorm. When draw.go reads existing pixels for alpha blending, it blindly assumes RGBA. The R and B channels are swapped when reading, and swapped again when writing, permanently corrupting the existing background pixels.
Proposed Fix: Check if the target format is BGRA and apply the correct swizzle when feeding existingData into pipe.SetPixel.
6. Missing GDI Synchronization
Symptom: Random black blocks or tearing during partial blits.
Root Cause: blitDamageRectsToWindow uses BitBlt directly after CPU writes to the DIB section. Without calling GdiFlush(), GDI commands and CPU memory writes race, resulting in stale data being presented.
Proposed Fix: Call GdiFlush() before and after GDI blit calls in blit_windows.go.
[Software Renderer/Windows] Severe Rendering Bugs, Freezes, and Performance Regressions
Note: This issue was analyzed and compiled by an AI coding agent assisting a human developer. We performed a deep dive into the software renderer fallback path and identified several critical issues affecting
gogpuandwgpu.Environment
github.com/gogpu/uiv0.1.40github.com/gogpu/ggv0.49.5github.com/gogpu/gogpuv0.43.0github.com/gogpu/wgpuv0.30.7Description
When running
gogpu/uiapplications (like thehelloorgalleryexamples) on Windows without hardware acceleration (falling back to theSoftware Renderer), the application exhibits severe bugs ranging from complete freezes to corrupted visuals and overlapping layouts.While the framework's development has done a fantastic job implementing modern GPU features (MSAA, Compositor Clipping) for hardware backends, these features heavily penalize the pure-Go CPU fallback. Additionally, several core WebGPU features are simply stubbed out or implemented incorrectly in the
hal/softwarebackend.Here is a detailed breakdown of the 6 root causes we identified during our debugging session:
1. Massive Performance Regression / "Freeze" (ADR-021 & MSAA)
Symptom: The application appears to completely freeze immediately upon startup.
Root Cause: A goroutine dump reveals the application isn't deadlocked; it is spinning at 100% CPU in
hal/software/raster.Rasterizeexecuting SPIR-V fragment shaders.In
gg v0.49.5, ADR-021 enforces full-screen renders (encodeSubmitSurfaceGrouped) and ignoresDamageRectswhen MSAA or grouped rendering is active. For a pure CPU software renderer, calculating complex SDF shaders for an entire 800x900 window (potentially at 4x MSAA) every frame creates an insurmountable fill-rate bottleneck. It takes minutes per frame, masquerading as a hard freeze.Proposed Fix: The engine should detect the Software adapter and explicitly disable MSAA and full-screen grouped rendering to force the
blitOnlypath with strictDamageRects.2. Missing OS Paint Event Propagation (
WM_PAINT)Symptom: Even when rendering is fast enough, the window often remains completely black until a pointer event (mouse move/click) forces a wake-up.
Root Cause:
gogpuv0.43.0 defaults to event-driven rendering (WithContinuousRender(false)). The Windows message loop ininternal/platform/platform_windows.gointerceptsWM_PAINTand validates the region, but never queues a redraw event.WaitEvents()remains blocked.Proposed Fix: Introduce an
EventPaintto the platform interface and triggerapp.RequestRedraw()when handlingWM_PAINT.3. Missing
DrawIndexedSupportSymptom: Text rendering is either completely missing or severely corrupted.
Root Cause: Modern text pipelines (like MSDF) use indexed quads. However,
DrawIndexedinhal/software/command.gois an empty no-op. All text draw calls are silently dropped.Proposed Fix: Implement
executeDrawIndexedand index-aware vertex fetchers (fetchTrianglesWithIndices) to properly resolve attributes.4. Ignored Dynamic Uniform Buffer Offsets
Symptom: Elements within lists (e.g., 1000 ListView items) fail to layout correctly and instead stack on top of each other at
(0,0).Root Cause: The software backend's
SetBindGroupignoresdynamicOffsets. The rasterizer always reads uniform data starting at offset0.Proposed Fix: Update
bufferBindingto retain base offsets, and adddynamicOffsetslogic insidebuildExecutionContextto slice the buffer correctly before executing the shader.5. Red/Blue (R/B) Channel Swap on Existing Pixels
Symptom: Backgrounds and borders turn orange instead of blue; colors degrade continuously on every redraw.
Root Cause: Windows offscreen DIBs use
BGRA8Unorm. Whendraw.goreads existing pixels for alpha blending, it blindly assumes RGBA. The R and B channels are swapped when reading, and swapped again when writing, permanently corrupting the existing background pixels.Proposed Fix: Check if the target format is BGRA and apply the correct swizzle when feeding
existingDataintopipe.SetPixel.6. Missing GDI Synchronization
Symptom: Random black blocks or tearing during partial blits.
Root Cause:
blitDamageRectsToWindowusesBitBltdirectly after CPU writes to the DIB section. Without callingGdiFlush(), GDI commands and CPU memory writes race, resulting in stale data being presented.Proposed Fix: Call
GdiFlush()before and after GDI blit calls inblit_windows.go.