Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Apps/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ if(APPLE)
set(ADDITIONAL_LIBRARIES PRIVATE ${JAVASCRIPTCORE_LIBRARY})
elseif(UNIX AND NOT ANDROID)
set(SOURCES ${SOURCES} "Source/App.X11.cpp")
set(ADDITIONAL_COMPILE_DEFINITIONS PRIVATE SKIP_EXTERNAL_TEXTURE_TESTS)
elseif(WIN32)
set(SOURCES ${SOURCES}
"Source/App.Win32.cpp"
Expand Down Expand Up @@ -89,6 +88,15 @@ if(GRAPHICS_API STREQUAL "D3D12")
target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS)
endif()

if(GRAPHICS_API STREQUAL "OpenGL")
# The OpenGL ExternalTexture backend imports single-sample GL_TEXTURE_2D
# handles only, so run the basic GetInfo/Set/Get/Update tests on any OpenGL
# build (Linux, or the OpenGLWindowsDevOnly dev path). The render-path tests
# (pixel readback) and the array/layer-index test require GL test helpers and
# array-texture support that this backend does not implement yet.
target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS SKIP_EXTERNAL_TEXTURE_ARRAY_TESTS)
endif()

add_test(NAME UnitTests COMMAND UnitTests)

# See https://gitlab.kitware.com/cmake/cmake/-/issues/23543
Expand Down
41 changes: 37 additions & 4 deletions Apps/UnitTests/Source/Helpers.OpenGL.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
#include <gtest/gtest.h>
#include "Helpers.h"

#include <GLES3/gl3.h>

#include <stdexcept>

namespace Helpers
{
Babylon::Graphics::TextureT CreateTexture(Babylon::Graphics::DeviceT, uint32_t, uint32_t, uint32_t, bool, uint32_t)
// The OpenGL ExternalTexture backend imports single-sample, non-array
// GL_TEXTURE_2D handles. glGenTextures/glTexImage2D require a current GL
// context, which the single-threaded Graphics::Device makes current on the
// calling thread between StartRenderingCurrentFrame/FinishRenderingCurrentFrame
// (the same thread the tests create textures on).
Babylon::Graphics::TextureT CreateTexture(Babylon::Graphics::DeviceT, uint32_t width, uint32_t height, uint32_t arraySize, bool renderTarget, uint32_t samples)
{
throw std::runtime_error{"not implemented"};
// The array / render-target / MSAA variants are only exercised by the
// render-path tests, which are skipped on this backend (see CMakeLists.txt).
if (arraySize != 1 || renderTarget || samples != 1)
{
throw std::runtime_error{"Helpers::CreateTexture(OpenGL): only single-sample, non-array GL_TEXTURE_2D textures are supported"};
}

GLint previousBinding = 0;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &previousBinding);

GLuint texture = 0;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

// Allocate a single, texture-complete mip level. The pixel contents are
// never read back by the enabled tests; only the queried dimensions and
// internal format matter.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(previousBinding));

EXPECT_EQ(glGetError(), static_cast<GLenum>(GL_NO_ERROR));

return texture;
}

void DestroyTexture(Babylon::Graphics::TextureT)
void DestroyTexture(Babylon::Graphics::TextureT texture)
{
throw std::runtime_error{"not implemented"};
GLuint handle = texture;
glDeleteTextures(1, &handle);
}

Babylon::Graphics::TextureT CreateTextureArrayWithData(Babylon::Graphics::DeviceT, uint32_t, uint32_t, const Color*, uint32_t)
Expand Down
44 changes: 30 additions & 14 deletions Apps/UnitTests/Source/Tests.ExternalTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ TEST(ExternalTexture, Construction)
device.StartRenderingCurrentFrame();

auto nativeTexture = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture};
Helpers::DestroyTexture(nativeTexture);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture, {}, 256, 256};

EXPECT_EQ(externalTexture.Width(), 256u);
EXPECT_EQ(externalTexture.Height(), 256u);

device.FinishRenderingCurrentFrame();

// The OpenGL backend does not take ownership of the handle, so the source texture must
// outlive the ExternalTexture (see ExternalTexture.h). Destroy it only now.
Helpers::DestroyTexture(nativeTexture);
#endif
}

Expand All @@ -43,8 +46,7 @@ TEST(ExternalTexture, CreateForJavaScript)
device.StartRenderingCurrentFrame();

auto nativeTexture = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture};
Helpers::DestroyTexture(nativeTexture);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture, {}, 256, 256};

std::promise<void> done{};

Expand All @@ -69,6 +71,10 @@ TEST(ExternalTexture, CreateForJavaScript)
done.get_future().wait();

device.FinishRenderingCurrentFrame();

// The GL backend doesn't own the handle; destroy the source texture only after bgfx has
// finished using it (see ExternalTexture.h).
Helpers::DestroyTexture(nativeTexture);
#endif
}

Expand All @@ -82,8 +88,7 @@ TEST(ExternalTexture, Update)
device.StartRenderingCurrentFrame();

auto nativeTexture = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture};
Helpers::DestroyTexture(nativeTexture);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture, {}, 256, 256};

EXPECT_EQ(externalTexture.Width(), 256u);
EXPECT_EQ(externalTexture.Height(), 256u);
Expand All @@ -94,13 +99,17 @@ TEST(ExternalTexture, Update)
device.StartRenderingCurrentFrame();

auto nativeTexture2 = Helpers::CreateTexture(device.GetPlatformInfo().Device, 128, 128);
externalTexture.Update(nativeTexture2);
Helpers::DestroyTexture(nativeTexture2);
externalTexture.Update(nativeTexture2, {}, {}, 128, 128);

EXPECT_EQ(externalTexture.Width(), 128u);
EXPECT_EQ(externalTexture.Height(), 128u);

device.FinishRenderingCurrentFrame();

// The GL backend doesn't own these handles; destroy them only after all rendering that
// referenced them has completed (see ExternalTexture.h).
Helpers::DestroyTexture(nativeTexture);
Helpers::DestroyTexture(nativeTexture2);
#endif
}

Expand All @@ -114,8 +123,7 @@ TEST(ExternalTexture, AddToContextAsyncAndUpdate)
device.StartRenderingCurrentFrame();

auto nativeTexture = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture};
Helpers::DestroyTexture(nativeTexture);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture, {}, 256, 256};

std::promise<void> addToContext{};
std::promise<void> promiseResolved{};
Expand Down Expand Up @@ -160,16 +168,20 @@ TEST(ExternalTexture, AddToContextAsyncAndUpdate)

// Update the external texture to a new texture.
auto nativeTexture2 = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256);
externalTexture.Update(nativeTexture2);
Helpers::DestroyTexture(nativeTexture2);
externalTexture.Update(nativeTexture2, {}, {}, 256, 256);

device.FinishRenderingCurrentFrame();

// The GL backend doesn't own these handles; destroy them only after all frames that
// referenced them have completed (see ExternalTexture.h).
Helpers::DestroyTexture(nativeTexture);
Helpers::DestroyTexture(nativeTexture2);
#endif
}

TEST(ExternalTexture, AddToContextAsyncWithLayerIndex)
{
#ifdef SKIP_EXTERNAL_TEXTURE_TESTS
#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_EXTERNAL_TEXTURE_ARRAY_TESTS)
GTEST_SKIP();
#else
Babylon::Graphics::Device device{g_deviceConfig};
Expand All @@ -179,7 +191,6 @@ TEST(ExternalTexture, AddToContextAsyncWithLayerIndex)
// Array texture (3 layers) so a non-zero layer index is valid.
auto nativeTexture = Helpers::CreateTexture(device.GetPlatformInfo().Device, 256, 256, 3);
Babylon::Plugins::ExternalTexture externalTexture{nativeTexture};
Helpers::DestroyTexture(nativeTexture);

std::promise<void> addToContext{};
std::promise<void> promiseResolved{};
Expand Down Expand Up @@ -220,5 +231,10 @@ TEST(ExternalTexture, AddToContextAsyncWithLayerIndex)

// get() (not wait()) so a rejected promise rethrows and fails the test.
EXPECT_NO_THROW(promiseResolved.get_future().get());

// The GL backend doesn't own the handle; destroy the source texture only after all
// rendering that referenced it has completed (see ExternalTexture.h). Harmless on
// D3D/Metal, which retain the native texture.
Helpers::DestroyTexture(nativeTexture);
#endif
}
20 changes: 18 additions & 2 deletions Plugins/ExternalTexture/Include/Babylon/Plugins/ExternalTexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,24 @@
namespace Babylon::Plugins
{
// All operations of this class must be called from the graphics thread unless otherwise noted.
//
// Ownership: on the OpenGL/OpenGL ES backend, ExternalTexture does NOT take ownership of the
// texture handle -- OpenGL has no way to reference-count a texture name -- so the caller must
// keep the source texture alive for as long as this ExternalTexture (and any JavaScript texture
// created from it) is in use. Deleting the handle earlier leaves a dangling GL name that bgfx
// later dereferences (e.g. glObjectLabel under a debug build), which is a fatal error on strict
// drivers. The D3D11/D3D12/Metal backends retain the native texture, so on those backends the
// caller may release its own reference immediately after construction.
class ExternalTexture final
{
public:
ExternalTexture(Graphics::TextureT, std::optional<Graphics::TextureFormatT> = {});
// width/height are required on the OpenGL/OpenGL ES backend and ignored on all
// others. BabylonNative's OpenGL renderer runs on an OpenGL ES 3.0 context, which
// provides no way to query a texture's dimensions from a bare handle
// (glGetTexLevelParameteriv was only added in ES 3.1). The caller that created the
// texture already knows its size, so it must supply it here. The D3D/Metal backends
// introspect the native texture and ignore these hints.
ExternalTexture(Graphics::TextureT, std::optional<Graphics::TextureFormatT> = {}, std::optional<uint32_t> width = {}, std::optional<uint32_t> height = {});
~ExternalTexture();

// Copy semantics
Expand Down Expand Up @@ -45,7 +59,9 @@ namespace Babylon::Plugins
Napi::Promise AddToContextAsync(Napi::Env, std::optional<uint16_t> layerIndex = {}) const;

// Updates to a new texture. If layerIndex is set, views only that array layer (single-slice).
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT> = {}, std::optional<uint16_t> layerIndex = {});
// width/height follow the same backend-specific contract as the constructor: required on
// OpenGL/OpenGL ES, ignored elsewhere.
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT> = {}, std::optional<uint16_t> layerIndex = {}, std::optional<uint32_t> width = {}, std::optional<uint32_t> height = {});

private:
class Impl;
Expand Down
6 changes: 3 additions & 3 deletions Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,16 @@ namespace Babylon::Plugins
{
public:
// Implemented in ExternalTexture_Shared.h
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>);
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint32_t>, std::optional<uint32_t>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>, std::optional<uint32_t>, std::optional<uint32_t>);

Graphics::TextureT Get() const
{
return m_ptr.get();
}

private:
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, Info& info)
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, std::optional<uint32_t> /*width*/, std::optional<uint32_t> /*height*/, Info& info)
{
winrt::com_ptr<ID3D11Resource> resource;
resource.copy_from(ptr);
Expand Down
6 changes: 3 additions & 3 deletions Plugins/ExternalTexture/Source/ExternalTexture_D3D12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,16 @@ namespace Babylon::Plugins
{
public:
// Implemented in ExternalTexture_Shared.h
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>);
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint32_t>, std::optional<uint32_t>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>, std::optional<uint32_t>, std::optional<uint32_t>);

Graphics::TextureT Get() const
{
return m_ptr.get();
}

private:
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, Info& info)
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, std::optional<uint32_t> /*width*/, std::optional<uint32_t> /*height*/, Info& info)
{
D3D12_RESOURCE_DESC desc = ptr->GetDesc();

Expand Down
6 changes: 3 additions & 3 deletions Plugins/ExternalTexture/Source/ExternalTexture_Metal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,16 @@ namespace Babylon::Plugins
{
public:
// Implemented in ExternalTexture_Shared.h
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>);
Impl(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint32_t>, std::optional<uint32_t>);
void Update(Graphics::TextureT, std::optional<Graphics::TextureFormatT>, std::optional<uint16_t>, std::optional<uint32_t>, std::optional<uint32_t>);

Graphics::TextureT Get() const
{
return m_ptr.get();
}

private:
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, Info& info)
static void GetInfo(Graphics::TextureT ptr, std::optional<Graphics::TextureFormatT> overrideFormat, std::optional<uint32_t> /*width*/, std::optional<uint32_t> /*height*/, Info& info)
{
if (ptr->textureType() != MTL::TextureType2D && ptr->textureType() != MTL::TextureType2DMultisample)
{
Expand Down
Loading
Loading