Skip to content
Merged
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
1 change: 1 addition & 0 deletions Apps/UnitTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ set(SOURCES
"Source/Tests.JavaScript.cpp"
"Source/Tests.NativeEngine.InstanceData.cpp"
"Source/Tests.NativeEngine.ImageFormats.cpp"
"Source/Tests.NativeEngine.DepthTextures.cpp"
"Source/Tests.NativeEngine.Teardown.cpp"
"Source/Tests.ShaderCache.cpp"
"Source/Tests.ShaderCompilation.cpp"
Expand Down
111 changes: 111 additions & 0 deletions Apps/UnitTests/Source/Tests.NativeEngine.DepthTextures.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include <gtest/gtest.h>
#include <Babylon/AppRuntime.h>
#include <Babylon/Graphics/Device.h>
#include <Babylon/Graphics/DeviceContext.h>
#include <Babylon/Graphics/FrameBuffer.h>
#include <Babylon/Graphics/Texture.h>
#include <Babylon/Plugins/NativeEngine.h>
#include <Babylon/Polyfills/Console.h>
#include <napi/pointer.h>

#include <array>
#include <chrono>
#include <future>
#include <iostream>
#include <string>

extern Babylon::Graphics::Configuration g_deviceConfig;

TEST(NativeEngineDepthTextures, ExposesReadableDepthAndPreservesFramebufferOwnership)
{
Babylon::Graphics::Device device{g_deviceConfig};
device.StartRenderingCurrentFrame();
Babylon::AppRuntime runtime{};
std::promise<std::string> completed;
auto future = completed.get_future();
runtime.Dispatch([&](Napi::Env env) {
std::string error;
try
{
device.AddToJavaScript(env);
Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) {
std::cout << message << std::endl;
});
Babylon::Plugins::NativeEngine::Initialize(env);
auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env);
auto frameScope = context.AcquireFrameCompletionScope();
auto engine = env.Global().Get("_native").As<Napi::Object>().Get("Engine").As<Napi::Function>().New({});
const auto createFrameBuffer = [&](Napi::Value texture, bool stencil, bool depth, uint32_t samples = 1) {
return engine.Get("createFrameBuffer").As<Napi::Function>().Call(engine, {
texture, Napi::Number::New(env, 16), Napi::Number::New(env, 16),
Napi::Boolean::New(env, stencil), Napi::Boolean::New(env, depth), Napi::Number::New(env, samples)});
};
const std::array<uint64_t, 3> renderTargetFlags{
BGFX_TEXTURE_RT, BGFX_TEXTURE_RT_MSAA_X2, BGFX_TEXTURE_RT_MSAA_X4};
for (size_t index = 0; index < renderTargetFlags.size(); ++index)
{
const uint32_t samples = 1u << index;
const uint64_t samplingFlag = index == 0 ? BGFX_TEXTURE_NONE : BGFX_TEXTURE_MSAA_SAMPLE;
const uint64_t flags = renderTargetFlags[index] | samplingFlag;
SCOPED_TRACE(samples);
for (const bool stencil : {false, true})
{
SCOPED_TRACE(stencil);
if (index != 0 &&
!bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::D24S8, flags) &&
(stencil || (!bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::D32F, flags) &&
!bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::D24, flags))))
{
std::cout << "Skipping unsupported readable depth MSAA: samples=" << samples
<< ", stencil=" << stencil << std::endl;
continue;
}
for (const bool depth : {false, true})
{
auto value = engine.Get("createTexture").As<Napi::Function>().Call(engine, {});
auto* texture = value.As<Napi::Pointer<Babylon::Graphics::Texture>>().Get();
if (!stencil && !depth)
{
EXPECT_THROW(createFrameBuffer(value, stencil, depth, samples), Napi::Error);
continue;
}
auto frameBufferValue = createFrameBuffer(value, stencil, depth, samples);
auto* frameBuffer = frameBufferValue.As<Napi::Pointer<Babylon::Graphics::FrameBuffer>>().Get();
EXPECT_TRUE(texture->IsValid());
EXPECT_EQ(texture->Width(), 16u);
EXPECT_EQ(texture->Height(), 16u);
EXPECT_EQ(texture->Flags() & BGFX_TEXTURE_RT_WRITE_ONLY, 0u);
EXPECT_EQ(texture->Flags() & BGFX_TEXTURE_MSAA_SAMPLE, samplingFlag);
EXPECT_EQ(texture->Flags() & BGFX_TEXTURE_RT_MSAA_MASK, renderTargetFlags[index]);
EXPECT_EQ(texture->Handle().idx, bgfx::getTexture(frameBuffer->Handle(), 0).idx);
EXPECT_TRUE(frameBuffer->HasDepth());
EXPECT_EQ(frameBuffer->HasStencil(), stencil);
texture->Dispose();
// Disposing the non-owning alias must leave the framebuffer usable.
frameBuffer->Clear(*context.GetActiveEncoder(),
BGFX_CLEAR_DEPTH | (stencil ? BGFX_CLEAR_STENCIL : 0), 0, 0.25f, 0);
frameBuffer->Dispose();
}
}
}
auto frameBufferValue = createFrameBuffer(env.Null(), false, true);
auto* frameBuffer = frameBufferValue.As<Napi::Pointer<Babylon::Graphics::FrameBuffer>>().Get();
EXPECT_TRUE(frameBuffer->HasDepth());
EXPECT_FALSE(frameBuffer->HasStencil());
frameBuffer->Dispose();
engine.Get("dispose").As<Napi::Function>().Call(engine, {});
}
catch (const std::exception& ex)
{
error = ex.what();
}
completed.set_value(std::move(error));
});
while (future.wait_for(std::chrono::milliseconds{16}) != std::future_status::ready)
{
device.FinishRenderingCurrentFrame();
device.StartRenderingCurrentFrame();
}
EXPECT_EQ(future.get(), "");
device.FinishRenderingCurrentFrame();
}
32 changes: 27 additions & 5 deletions Plugins/NativeEngine/Source/NativeEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2467,10 +2467,16 @@ namespace Babylon
const uint32_t samples = info[5].IsUndefined() ? 1 : info[5].As<Napi::Number>().Uint32Value();

// A single render target is just the zero-or-one color attachment case of the shared implementation.
const bool requestDepthStencilTexture = texture != nullptr && !texture->IsValid();
if (requestDepthStencilTexture && !generateDepth && !generateStencilBuffer)
{
throw Napi::Error::New(info.Env(), "An uninitialized texture requires a depth/stencil attachment");
}
Graphics::Texture* const colorTextures[]{texture};
const gsl::span<Graphics::Texture* const> colorAttachments{colorTextures, texture != nullptr ? 1u : 0u};
const gsl::span<Graphics::Texture* const> colorAttachments{colorTextures, texture != nullptr && !requestDepthStencilTexture ? 1u : 0u};

return CreateFrameBufferImpl(info.Env(), colorAttachments, width, height, generateStencilBuffer, generateDepth, samples);
return CreateFrameBufferImpl(info.Env(), colorAttachments, width, height, generateStencilBuffer, generateDepth, samples,
requestDepthStencilTexture ? texture : nullptr);
}

Napi::Value NativeEngine::CreateMultiFrameBuffer(const Napi::CallbackInfo& info)
Expand Down Expand Up @@ -2498,7 +2504,7 @@ namespace Babylon
return CreateFrameBufferImpl(info.Env(), gsl::span<Graphics::Texture* const>{colorTextures.data(), colorCount}, width, height, generateStencilBuffer, generateDepth, samples);
}

Napi::Value NativeEngine::CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples)
Napi::Value NativeEngine::CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, Graphics::Texture* depthStencilTexture)
{
const bgfx::Caps* caps = bgfx::getCaps();
const uint32_t colorCount = static_cast<uint32_t>(colorTextures.size());
Expand Down Expand Up @@ -2527,14 +2533,22 @@ namespace Babylon

bgfx::TextureHandle depthStencilTextureHandle = BGFX_INVALID_HANDLE;
int8_t depthStencilAttachmentIndex = -1;
bgfx::TextureFormat::Enum depthStencilTextureFormat = bgfx::TextureFormat::Unknown;
uint64_t depthStencilTextureFlags = BGFX_TEXTURE_NONE;
if (generateStencilBuffer || generateDepth)
{
if (generateStencilBuffer && !generateDepth)
{
JsConsoleLogger::LogWarn(env, "Stencil without depth is not supported, assuming depth and stencil");
}

auto flags = BGFX_TEXTURE_RT_WRITE_ONLY | RenderTargetSamplesToBgfxMsaaFlag(samples);
const auto msaaFlag = RenderTargetSamplesToBgfxMsaaFlag(samples);
auto flags = BGFX_TEXTURE_RT_WRITE_ONLY | msaaFlag;
if (depthStencilTexture != nullptr)
{
// A standalone texture must be readable. Multisampled depth is sampled directly, not resolved.
flags = msaaFlag == BGFX_TEXTURE_NONE ? BGFX_TEXTURE_RT : msaaFlag | BGFX_TEXTURE_MSAA_SAMPLE;
}

// Pick a depth(/stencil) format the active renderer actually supports as an RT.
// Plain D32 is not a valid D3D11 depth RT (bgfx maps it to R24G8 with no DSV), and
Expand All @@ -2561,6 +2575,8 @@ namespace Babylon
throw Napi::Error::New(env, "No supported depth/stencil texture format for frame buffer");
}
depthStencilTextureHandle = bgfx::createTexture2D(width, height, false, 1, depthStencilFormat, flags);
depthStencilTextureFormat = depthStencilFormat;
depthStencilTextureFlags = flags;

// bgfx doesn't add flag D3D11_RESOURCE_MISC_GENERATE_MIPS for depth textures (missing that flag will crash D3D with resolving)
// And not sure it makes sense to generate mipmaps from a depth buffer with exponential values.
Expand All @@ -2581,7 +2597,13 @@ namespace Babylon
throw Napi::Error::New(env, "Failed to create frame buffer");
}

Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, generateDepth, generateStencilBuffer, depthStencilAttachmentIndex);
const bool hasDepthAttachment = generateDepth || generateStencilBuffer;
Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, hasDepthAttachment, generateStencilBuffer, depthStencilAttachmentIndex);
if (depthStencilTexture != nullptr)
{
// The framebuffer owns its depth attachment; expose a non-owning texture for sampling.
depthStencilTexture->Attach(depthStencilTextureHandle, width, height, false, 1, depthStencilTextureFormat, depthStencilTextureFlags);
}
return Napi::Pointer<Graphics::FrameBuffer>::Create(env, frameBuffer, Napi::NapiPointerDeleter(frameBuffer));
}

Expand Down
2 changes: 1 addition & 1 deletion Plugins/NativeEngine/Source/NativeEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ namespace Babylon
Napi::Value ReadTexture(const Napi::CallbackInfo& info);
Napi::Value CreateFrameBuffer(const Napi::CallbackInfo& info);
Napi::Value CreateMultiFrameBuffer(const Napi::CallbackInfo& info);
Napi::Value CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples);
Napi::Value CreateFrameBufferImpl(Napi::Env env, gsl::span<Graphics::Texture* const> colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, Graphics::Texture* depthStencilTexture = nullptr);
void DeleteFrameBuffer(NativeDataStream::Reader& data);
void BindFrameBuffer(NativeDataStream::Reader& data);
void UnbindFrameBuffer(NativeDataStream::Reader& data);
Expand Down
Loading