Skip to content
Open
2 changes: 0 additions & 2 deletions Apps/Playground/Scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2211,8 +2211,6 @@
{
"title": "Test updateTextureData",
"playgroundId": "#EVX1DH#80",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "testUpdateTextureData.png"
},
{
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,25 @@ void main() {
// ── Texture sampling: texelFetch() ──────────────────────────────
ivec2 texCoord = ivec2(gl_FragCoord.xy);
vec4 fetched = texelFetch(uSampler2D, texCoord, 0);

// texelFetch coordinates are flipped vertically by the shader compiler, which has to clone
// the coordinate expression to reference it twice. Cover the operand shapes that appear in
// Babylon shaders beyond a plain symbol: a constructor, a binary expression, a nested
// constructor over a float expression, a nested constructor over integer binaries, built-in
// calls, and a non-constant lod. Integer multiply, divide, modulo and bitwise operators are
// deliberately avoided: Babylon Native builds glslang and SPIRV-Cross in their WEBMIN
// configurations, which do not support them, so they would fail for reasons that have nothing
// to do with texel coordinates.
int fetchIndex = int(gl_FragCoord.x) + int(gl_FragCoord.y);
int fetchLod = fetchIndex - fetchIndex;
ivec2 fetchSize = textureSize(uSampler2D, 0);
vec4 fetchedCtor = texelFetch(uSampler2D, ivec2(gl_FragCoord.xy), 0);
vec4 fetchedOffset = texelFetch(uSampler2D, texCoord + ivec2(1, 1), 0);
vec4 fetchedScaled = texelFetch(uSampler2D, ivec2(vUV * vec2(fetchSize)), 0);
vec4 fetchedNested = texelFetch(uSampler2D, ivec2(fetchSize.x - texCoord.x, fetchSize.y - texCoord.y), 0);
vec4 fetchedCall = texelFetch(uSampler2D, ivec2(abs(texCoord.x), abs(texCoord.y)), 0);
vec4 fetchedLod = texelFetch(uSampler2D, texCoord.yx, fetchLod);
vec4 fetchedComplex = fetchedCtor + fetchedOffset + fetchedScaled + fetchedNested + fetchedCall + fetchedLod;

// ── Texture sampling: textureSize() ─────────────────────────────
ivec2 size2d = textureSize(uSampler2D, 0);
Expand Down Expand Up @@ -951,7 +970,7 @@ void main() {
gl_FragDepth = fragDepth;

// ── Output (blend in splat color contribution) ────────────────────
fragColor = finalColor + vSplatColor * 0.001;
fragColor = finalColor + vSplatColor * 0.001 + fetchedComplex * 0.0001;
}
`;

Expand Down
15 changes: 15 additions & 0 deletions Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace Babylon::Graphics
void Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags);
void Update2D(uint16_t layer, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch = UINT16_MAX);

void Create3D(uint16_t width, uint16_t height, uint16_t depth, bool hasMips, bgfx::TextureFormat::Enum format, uint64_t flags);
void Update3D(uint8_t mip, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const bgfx::Memory* mem);

void CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags);
void UpdateCube(uint16_t layer, uint8_t side, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch = UINT16_MAX);

Expand All @@ -31,7 +34,10 @@ namespace Babylon::Graphics
uint16_t Width() const;
uint16_t Height() const;
bool HasMips() const;
bool IsCube() const;
bool Is3D() const;
uint16_t NumLayers() const;
uint16_t Depth() const;
bgfx::TextureFormat::Enum Format() const;
uint64_t Flags() const;
uint32_t SamplerFlags() const;
Expand All @@ -50,12 +56,21 @@ namespace Babylon::Graphics
void BlitViewId(bgfx::ViewId viewId) { m_blitViewId = viewId; }

private:
// Resets every piece of shape metadata to its default. Each Create*/Attach calls this
// before assigning the subset that applies to it, so a field a given path does not set
// (m_depth is only meaningful for Create3D) cannot survive from the previous, differently
// shaped texture this object described.
void ResetMetadata();

bgfx::TextureHandle m_handle{bgfx::kInvalidHandle};
bool m_ownsHandle{false};
uint16_t m_width{0};
uint16_t m_height{0};
bool m_hasMips{false};
bool m_isCube{false};
bool m_is3D{false};
uint16_t m_numLayers{0};
uint16_t m_depth{0};
bgfx::TextureFormat::Enum m_format{bgfx::TextureFormat::Enum::Unknown};
uint64_t m_flags{BGFX_TEXTURE_NONE};
uint32_t m_samplerFlags{BGFX_SAMPLER_NONE};
Expand Down
59 changes: 59 additions & 0 deletions Core/Graphics/Source/Texture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,23 @@ namespace Babylon::Graphics
return bgfx::isValid(m_handle);
}

void Texture::ResetMetadata()
{
m_width = 0;
m_height = 0;
m_depth = 0;
m_hasMips = false;
m_isCube = false;
m_is3D = false;
m_numLayers = 0;
m_format = bgfx::TextureFormat::Enum::Unknown;
m_flags = BGFX_TEXTURE_NONE;
}

void Texture::Create2D(uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags)
{
Dispose();
ResetMetadata();

// make sure render targets are filled with 0 : https://registry.khronos.org/webgl/specs/latest/1.0/#TEXIMAGE2D
const auto* mem = (flags & BGFX_TEXTURE_RT) ? GetZeroImageMemory(width, height, hasMips, numLayers, format) : nullptr;
Expand All @@ -71,9 +85,37 @@ namespace Babylon::Graphics
bgfx::updateTexture2D(m_handle, layer, mip, x, y, width, height, mem, pitch);
}

void Texture::Create3D(uint16_t width, uint16_t height, uint16_t depth, bool hasMips, bgfx::TextureFormat::Enum format, uint64_t flags)
{
Dispose();
ResetMetadata();

m_handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags);
if (!bgfx::isValid(m_handle))
{
throw std::runtime_error{"Failed to create 3D texture"};
}

m_ownsHandle = true;
m_width = width;
m_height = height;
m_depth = depth;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Reviewed by Copilot on behalf of @bghgary]

m_depth is only ever assigned here, and Dispose() clears just the handle — so a Texture re-created as 2D or cube after having been 3D keeps the old depth and Depth() returns a stale value. The sibling Create* methods each reset m_is3D but none of them reset m_depth.

Each Create* hand-assigning its own subset of the metadata is what made the dead m_is3D store possible too; a shared reset would close both.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a3241c1. m_depth was assigned only by Create3D, and Dispose() clears just the handle, so a Texture re-created as 2D or cube after having been 3D kept reporting the old depth.

I took the shared-reset route you suggested rather than adding one more hand-written assignment. There is now a private ResetMetadata() that returns every shape field to its default, and Create2D, Create3D, CreateCube and Attach all call it immediately after Dispose() before assigning the subset that applies to them. That also let me delete the scattered m_isCube = false; m_is3D = false; lines, which is what made the class of bug possible in the first place: any field a given path forgets is now defaulted rather than inherited from the previous, differently shaped texture.

m_hasMips = hasMips;
m_numLayers = 1;
m_format = format;
m_flags = flags;
m_is3D = true;
}

void Texture::Update3D(uint8_t mip, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const bgfx::Memory* mem)
{
bgfx::updateTexture3D(m_handle, mip, x, y, z, width, height, depth, mem);
}

void Texture::CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags)
{
Dispose();
ResetMetadata();

m_handle = bgfx::createTextureCube(size, hasMips, numLayers, format, flags);
m_ownsHandle = true;
Expand All @@ -83,6 +125,7 @@ namespace Babylon::Graphics
m_numLayers = numLayers;
m_format = format;
m_flags = flags;
m_isCube = true;
}

void Texture::UpdateCube(uint16_t layer, uint8_t side, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch)
Expand All @@ -93,6 +136,7 @@ namespace Babylon::Graphics
void Texture::Attach(bgfx::TextureHandle handle, bool ownsHandle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags)
{
Dispose();
ResetMetadata();

assert(bgfx::isValid(handle));
m_handle = handle;
Expand Down Expand Up @@ -125,11 +169,26 @@ namespace Babylon::Graphics
return m_hasMips;
}

bool Texture::IsCube() const
{
return m_isCube;
}

bool Texture::Is3D() const
{
return m_is3D;
}

uint16_t Texture::NumLayers() const
{
return m_numLayers;
}

uint16_t Texture::Depth() const
{
return m_depth;
}

bgfx::TextureFormat::Enum Texture::Format() const
{
return m_format;
Expand Down
Loading
Loading