Summary
The WebGL2 renderer (video.player.type = webgl2) renders a black screen because the immutable texture storage is never allocated: gl.texStorage2D() is called with an unsized internal format.
Location: src/modules/player/webgl2/webgl2-player.ts → allocateStorage():
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB, width, height);
gl.RGB (0x1907) is not a sized internal format, and per the WebGL2 spec texStorage2D requires a sized internal format (e.g. R8, RG8, RGB8, RGBA8…). The call fails with INVALID_ENUM, the storage is never defined, and every per-frame texSubImage2D(..., this.$video) upload then fails too:
GL_INVALID_ENUM: glTexStorage2D: The <internalformat> is not a sized or compressed texture format or it is unsupported in the current context.
GL_INVALID_OPERATION: glCopySubTextureCHROMIUM: The destination level of the destination texture must be defined.
Result: black canvas (no video frames), no GL error surfaced to JS.
Reproduction
- Set
video.player.type to webgl2 in Better xCloud settings.
- Start any stream → black video output.
Confirmed outside xCloud too, in a plain WebGL2 context with a real <video> element (Edge, ANGLE/D3D11), by rendering the texture to an FBO and reading pixels back:
| internalformat |
texStorage2D |
framebuffer |
readPixels |
gl.RGB |
INVALID_ENUM (0x500) |
INCOMPLETE |
100 % black (avg 0, 0 % non-zero) |
gl.RGB8 |
OK |
COMPLETE |
real video content (avg ~130, 98.5 % non-zero) |
Fix
One line in allocateStorage():
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGB8, width, height);
texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGB, gl.UNSIGNED_BYTE, this.$video) in updateFrame() is fine as-is — unsized formats are valid as upload formats for texSubImage2D.
Impact
Only sessions that use the WebGL2 renderer (not the default player type). The WebGPU renderer has the same pattern (paramsBuffer/uniform path) but is unaffected. This is fixed and shipped in my fork release better-xcloud-perf v1.4.0 (patch 18); happy to send the TS change if preferred.
Summary
The WebGL2 renderer (
video.player.type = webgl2) renders a black screen because the immutable texture storage is never allocated:gl.texStorage2D()is called with an unsized internal format.Location:
src/modules/player/webgl2/webgl2-player.ts→allocateStorage():gl.RGB(0x1907) is not a sized internal format, and per the WebGL2 spectexStorage2Drequires a sized internal format (e.g.R8,RG8,RGB8,RGBA8…). The call fails withINVALID_ENUM, the storage is never defined, and every per-frametexSubImage2D(..., this.$video)upload then fails too:Result: black canvas (no video frames), no GL error surfaced to JS.
Reproduction
video.player.typetowebgl2in Better xCloud settings.Confirmed outside xCloud too, in a plain WebGL2 context with a real
<video>element (Edge, ANGLE/D3D11), by rendering the texture to an FBO and reading pixels back:gl.RGBINVALID_ENUM(0x500)INCOMPLETEgl.RGB8COMPLETEFix
One line in
allocateStorage():texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGB, gl.UNSIGNED_BYTE, this.$video)inupdateFrame()is fine as-is — unsized formats are valid as upload formats fortexSubImage2D.Impact
Only sessions that use the WebGL2 renderer (not the default player type). The WebGPU renderer has the same pattern (
paramsBuffer/uniform path) but is unaffected. This is fixed and shipped in my fork releasebetter-xcloud-perf v1.4.0(patch 18); happy to send the TS change if preferred.