@@ -959,6 +959,7 @@ namespace Babylon
959959 InstanceMethod (" initializeTexture" , &NativeEngine::InitializeTexture),
960960 InstanceMethod (" loadTexture" , &NativeEngine::LoadTexture),
961961 InstanceMethod (" loadRawTexture" , &NativeEngine::LoadRawTexture),
962+ InstanceMethod (" updateTextureData" , &NativeEngine::UpdateTextureData),
962963 InstanceMethod (" loadRawTexture2DArray" , &NativeEngine::LoadRawTexture2DArray),
963964 InstanceMethod (" loadCubeTexture" , &NativeEngine::LoadCubeTexture),
964965 InstanceMethod (" loadCubeTextureWithMips" , &NativeEngine::LoadCubeTextureWithMips),
@@ -1654,6 +1655,77 @@ namespace Babylon
16541655#endif
16551656 }
16561657
1658+ void NativeEngine::UpdateTextureData (const Napi::CallbackInfo& info)
1659+ {
1660+ const auto texture{info[0 ].As <Napi::Pointer<Graphics::Texture>>().Get ()};
1661+ const auto data{info[1 ].As <Napi::TypedArray>()};
1662+ const auto x{static_cast <uint16_t >(info[2 ].As <Napi::Number>().Uint32Value ())};
1663+ const auto y{static_cast <uint16_t >(info[3 ].As <Napi::Number>().Uint32Value ())};
1664+ const auto width{static_cast <uint16_t >(info[4 ].As <Napi::Number>().Uint32Value ())};
1665+ const auto height{static_cast <uint16_t >(info[5 ].As <Napi::Number>().Uint32Value ())};
1666+ const uint16_t layer{info.Length () > 6 && !info[6 ].IsUndefined () ? static_cast <uint16_t >(info[6 ].As <Napi::Number>().Uint32Value ()) : static_cast <uint16_t >(0 )};
1667+ const uint8_t mip{info.Length () > 7 && !info[7 ].IsUndefined () ? static_cast <uint8_t >(info[7 ].As <Napi::Number>().Uint32Value ()) : static_cast <uint8_t >(0 )};
1668+ const bool invertY{info.Length () > 8 && !info[8 ].IsUndefined () ? info[8 ].As <Napi::Boolean>().Value () : false };
1669+
1670+ if (texture == nullptr || !texture->IsValid ())
1671+ {
1672+ throw Napi::Error::New (info.Env (), " updateTextureData called on an invalid texture" );
1673+ }
1674+
1675+ // Validate the (JS-controlled) update rectangle against the mip-level extents before handing it to
1676+ // bgfx, so an out-of-range origin/size can't drive an out-of-bounds read of the source buffer below.
1677+ uint32_t mipWidth{static_cast <uint32_t >(texture->Width ()) >> mip};
1678+ uint32_t mipHeight{static_cast <uint32_t >(texture->Height ()) >> mip};
1679+ if (mipWidth == 0 )
1680+ {
1681+ mipWidth = 1 ;
1682+ }
1683+ if (mipHeight == 0 )
1684+ {
1685+ mipHeight = 1 ;
1686+ }
1687+ const uint16_t numLayers{texture->NumLayers () > 0 ? texture->NumLayers () : static_cast <uint16_t >(1 )};
1688+ if (width == 0 || height == 0 ||
1689+ static_cast <uint32_t >(x) + width > mipWidth ||
1690+ static_cast <uint32_t >(y) + height > mipHeight ||
1691+ layer >= numLayers)
1692+ {
1693+ throw Napi::Error::New (info.Env (), " updateTextureData region is out of bounds" );
1694+ }
1695+
1696+ // Size of the source rectangle in the texture's own format. bgfx is always linked (bimg is not, in
1697+ // builds without image loading), so size the upload with bgfx::calcTextureSize rather than bimg.
1698+ bgfx::TextureInfo textureInfo;
1699+ bgfx::calcTextureSize (textureInfo, width, height, 1 , false , false , 1 , texture->Format ());
1700+ const uint32_t requiredSize{textureInfo.storageSize };
1701+ if (requiredSize == 0 || data.ByteLength () < requiredSize)
1702+ {
1703+ throw Napi::Error::New (info.Env (), " updateTextureData data size does not match width, height, and texture format" );
1704+ }
1705+
1706+ const auto bytes{static_cast <uint8_t *>(data.ArrayBuffer ().Data ()) + data.ByteOffset ()};
1707+
1708+ // Match the vertical orientation the base upload applies (PrepareImage flips the whole image when
1709+ // originBottomLeft ? invertY : !invertY). To land a sub-rectangle at the same place, flip it to the
1710+ // mirrored Y origin and reverse its rows so row 0 of the source lines up with the flipped base data.
1711+ const bool flip{bgfx::getCaps ()->originBottomLeft ? invertY : !invertY};
1712+ const uint16_t targetY{flip ? static_cast <uint16_t >(mipHeight - y - height) : y};
1713+ const bgfx::Memory* mem{bgfx::alloc (requiredSize)};
1714+ if (flip)
1715+ {
1716+ const uint32_t rowBytes{requiredSize / height};
1717+ for (uint16_t row = 0 ; row < height; ++row)
1718+ {
1719+ std::memcpy (mem->data + static_cast <size_t >(row) * rowBytes, bytes + static_cast <size_t >(height - 1 - row) * rowBytes, rowBytes);
1720+ }
1721+ }
1722+ else
1723+ {
1724+ std::memcpy (mem->data , bytes, requiredSize);
1725+ }
1726+ texture->Update2D (layer, mip, x, targetY, width, height, mem);
1727+ }
1728+
16571729 void NativeEngine::LoadRawTexture2DArray (const Napi::CallbackInfo& info)
16581730 {
16591731#ifndef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES
0 commit comments