[rlsw] `LoadRenderTexture()` framebuffer is `INCOMPLETE_ATTACHMENT` unless `SW_FRAMEBUFFER_DEPTH_TYPE == D32`

Open Beginner friendly
#5,978 5 comments 2 reactions 0 assignees View on GitHub

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

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from raysan5/raylib

All issues in raysan5/raylib

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.