Skip to content

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

Description

@HurricanVD

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions