[rlsw] `LoadRenderTexture()` framebuffer is `INCOMPLETE_ATTACHMENT` unless `SW_FRAMEBUFFER_DEPTH_TYPE == D32`
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 76/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- c
- Domain
- computer-graphics
Research direction
Start with swTexStorage2D() in src/external/rlsw.h and rlLoadTextureDepth() in src/rlgl.h, then reproduce with SW_FRAMEBUFFER_DEPTH_TYPE set to D8 or D16 using LoadRenderTexture(). Verify the depth attachment matches the compiled rasterizer format and that glCheckFramebufferStatus() reports a complete framebuffer while the existing mismatch check remains intact.
Written by the indexing model from the issue text.
Description
Issue description
With the software renderer (GRAPHICS_API_OPENGL_SOFTWARE), LoadRenderTexture() produces an incomplete framebuffer whenever rlsw is compiled with a depth type other than the default D32 (i.e. SW_FRAMEBUFFER_DEPTH_TYPE = D8 or D16). rlFramebufferComplete() logs WARNING: FBO: [ID 1] Framebuffer has incomplete attachment and any BeginTextureMode()/EndTextureMode() then renders into nothing (the render texture stays empty). The default D32 build works, so the bug is latent and only appears once you pick a smaller depth buffer (e.g. to save memory on a constrained target).
Root cause
The rlsw rasterizer is bound at compile time to exactly one depth format, SW_FRAMEBUFFER_DEPTH_FORMAT (from SW_FRAMEBUFFER_DEPTH_TYPE); depth is read/written via the compile-time SW_FRAMEBUFFER_DEPTH_GET/SET macros with a fixed stride. So every depth attachment must have that format, and swCheckFramebufferStatus() enforces it:
// external/rlsw.h - swCheckFramebufferStatus()
if (RLSW.depthBuffer->format != SW_FRAMEBUFFER_DEPTH_FORMAT)
return SW_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
But the FBO depth attachment is created with a fixed format that ignores the compiled type, at two spots:
1. src/rlgl.h, rlLoadTextureDepth() software branch hard-codes GL_DEPTH_COMPONENT32 (its own comment even warns about it):
#elif defined(GRAPHICS_API_OPENGL_SOFTWARE)
// NOTE: Renderbuffers are the same type of object as textures in rlsw
// WARNING: Ensure that the depth format is the one specified at rlsw compilation
glGenRenderbuffers(1, &id);
glBindRenderbuffer(GL_RENDERBUFFER, id);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height); // hard-coded
glBindRenderbuffer(GL_RENDERBUFFER, 0);
#endif
2. src/external/rlsw.h, swTexStorage2D() maps every depth internal format to D16/D32 regardless of the compiled type (there is no GL_DEPTH_COMPONENT8 enum):
case SW_DEPTH_COMPONENT16: pixelFormat = SW_PIXELFORMAT_DEPTH_D16; break;
case SW_DEPTH_COMPONENT24: pixelFormat = SW_PIXELFORMAT_DEPTH_D32; break;
case SW_DEPTH_COMPONENT32: pixelFormat = SW_PIXELFORMAT_DEPTH_D32; break;
case SW_DEPTH_COMPONENT32F: pixelFormat = SW_PIXELFORMAT_DEPTH_D32; break;
On a D8 build the FBO depth attachment ends up D32, so D32 != D8 -> INCOMPLETE_ATTACHMENT. On the default D32 build the two happen to agree, which is why the default build works.
Steps to reproduce
Build rlsw with a non-default depth type and create a render texture:
// rlsw compiled with -DSW_FRAMEBUFFER_DEPTH_TYPE=D8 (or D16)
InitWindow(800, 450, "rt");
RenderTexture2D t = LoadRenderTexture(160, 90);
// console: "WARNING: FBO: [ID 1] Framebuffer has incomplete attachment"
The stock examples/core/core_smooth_pixelperfect then shows an empty canvas (text overlays draw fine; the up-scaled render-texture region stays blank).
Minimal engine-free reproduction (runs on any host). rlsw is pure software, so the completeness path can be driven without a window/GL: #define RLSW_IMPLEMENTATION, swInit(), then create a color texture + a depth renderbuffer via glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, ...), attach both, and print glCheckFramebufferStatus() - i.e. exactly the gl* -> sw* calls rlgl makes. Built with native x86_64 mingw-gcc against the unmodified upstream rlsw.h, changing only SW_FRAMEBUFFER_DEPTH_TYPE:
| SW_FRAMEBUFFER_DEPTH_TYPE | framebuffer status |
|---|---|
D8 |
0x8cd6 INCOMPLETE_ATTACHMENT |
D16 |
0x8cd6 INCOMPLETE_ATTACHMENT |
D32 (default) |
0x8cd5 COMPLETE |
Proposed fix
Any depth attachment used by the rasterizer must have the one compile-time format (SW_FRAMEBUFFER_DEPTH_FORMAT) - depth uses the fixed-stride SW_FRAMEBUFFER_DEPTH_GET/SET macros, so a different-format depth buffer would also be misread, not merely incomplete. Two ways:
Option A - minimal (rlsw only). Coerce any depth request in swTexStorage2D() to the compiled format:
case SW_DEPTH_COMPONENT16:
case SW_DEPTH_COMPONENT24:
case SW_DEPTH_COMPONENT32:
case SW_DEPTH_COMPONENT32F: pixelFormat = SW_FRAMEBUFFER_DEPTH_FORMAT; break;
One line, one file, covers D8 (no GL enum exists for it); default D32 unchanged.
Option B - explicit format (rlsw + rlgl). If you prefer the format named rather than silently coerced: add a (non-standard) SW_DEPTH_COMPONENT8 to SWinternalformat (the internal SW_PIXELFORMAT_DEPTH_D8 already exists) and map it in swTexStorage2D(); then in rlgl.h rlLoadTextureDepth() (software) replace the hard-coded GL_DEPTH_COMPONENT32 with the enum matching the compiled SW_FRAMEBUFFER_DEPTH_TYPE. More faithful, but touches two files, needs a non-standard enum, and adds no capability (the rasterizer is single-format, so rlLoadTextureDepth must request exactly the compiled format anyway).
Either way, keep the depthBuffer->format != SW_FRAMEBUFFER_DEPTH_FORMAT check in swCheckFramebufferStatus() as the backstop. (I used Option A in my port and verified it - table above.)
Environment
- raylib 6.1-dev @
f00317be GRAPHICS_API_OPENGL_SOFTWARE(rlsw),SW_FRAMEBUFFER_DEPTH_TYPE = D8- Found while porting raylib to AmigaOS 3.2 (68k, software renderer); the minimal repro above was run and confirmed on Windows x86_64 (mingw-gcc 15.2) against the unmodified upstream
rlsw.h.
- Dominant language
- C
- Stars
- 34.8k
- Forks
- 3.3k
- Avg merge
- 17h 56m
- Merged PRs (30d)
- 59
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from raysan5/raylib
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
windowing
Difficulty 4/5 3-5 days Newbie friendliness 50/100
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
[rcore][drm] Link failure for `eglGetPlatformDisplay()` on Mali GPUs with stripped EGL symbol tables Openplatform: DRM/RPI
Difficulty 4/5 3-5 days Newbie friendliness 54/100
-
examples
Difficulty 3/5 1-2 days Newbie friendliness 48/100
Similar issues
-
level/task module/gcp type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
docs
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
P3 sonic-vpp
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
sonic-net/sonic-buildimage#29662 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 94/100
spack/spack-packages#6586 ·
-
category:port-update
Difficulty 2/5 1-3 hours Newbie friendliness 72/100