Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7304a31
nanovg Compositor
CedricGuillemet Mar 7, 2025
05ddd44
method visibility
CedricGuillemet Mar 7, 2025
09aa481
a bit of clean up
CedricGuillemet Mar 7, 2025
2aacc5e
remove 2nd encoder
CedricGuillemet Mar 11, 2025
6a4f4f3
Merge branch 'CanvasTest' of https://github.com/BabylonJS/BabylonNati…
CedricGuillemet Mar 18, 2025
704650a
Merge branch 'master' of https://github.com/BabylonJS/BabylonNative i…
CedricGuillemet Mar 18, 2025
d1223cb
filter stack PoC
CedricGuillemet Mar 19, 2025
36fc856
parse filter string with regex, wire up glnvg__stroke
Pheo Mar 20, 2025
da979cd
add basic framebuffer management
Pheo Mar 22, 2025
d63e3bb
Multi-pass rendering
Pheo Mar 23, 2025
b66f2f9
Move bind/unbind into Context.cpp acquire/release
Pheo Mar 25, 2025
d21d7da
WIP - filterstack uniform handling, separated gaussian blur
Pheo Mar 25, 2025
93e1af1
fixed - working filterstack uniform+program handling
Pheo Mar 26, 2025
b64e17a
wire up radius/strength to blur filter, support alpha blur
Pheo Mar 26, 2025
19b6d52
clean up comments
Pheo Mar 26, 2025
13a3b8e
move FrameBufferPool into own class
Pheo Mar 30, 2025
63fcb8e
handle half-texel in blur, clean up filterstack uniform+program setup
Pheo Mar 31, 2025
6deeb4c
update compiled shaders
Pheo Mar 31, 2025
7651928
cleanup comments, cleanup FrameBufferPool methods
Pheo Mar 31, 2025
ca410ca
Merge branch 'CanvasTest' of https://github.com/BabylonJS/BabylonNati…
Pheo Mar 31, 2025
7ff2c0c
PR Comments
Pheo Apr 1, 2025
c39d793
PR Comment: Create framebuffers only when needed
Pheo Apr 1, 2025
783566a
bit of cleanup around framebuffer mgmt, add todos in shader
Pheo Apr 2, 2025
287ff01
clear released framebuffers, bind canvas framebuffer for final filter…
Pheo Apr 2, 2025
62ec4b7
support filter passes in glnvg__fill, glnvg__convexfill
Pheo Apr 3, 2025
d514391
fix: pass second texture to glnvg__triangles, gradient text works again
Pheo Apr 3, 2025
50697ff
Merge branch 'CanvasTest' of https://github.com/BabylonJS/BabylonNati…
Pheo Apr 7, 2025
86095b3
do bgfx::ReleaseFn, bx::memSet for FrameBufferPool textures
Pheo Apr 7, 2025
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
8 changes: 7 additions & 1 deletion Apps/Playground/Scripts/experience.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ CreateBoxAsync(scene).then(function () {
context.lineTo(140, 140);
context.stroke();

// filter blur text
context.filter = "blur(1.25px)";
context.fillStyle = "White";
context.font = `bold ${50}px monospace`;
context.fillText("BLUR TEST BLUR TEST", 100, 246);
context.filter = "none";

// Draw lines
context.strokeStyle = "black";
["butt", "round", "square"].forEach((lineCap, i) => {
Expand Down Expand Up @@ -248,7 +255,6 @@ CreateBoxAsync(scene).then(function () {
});
}, undefined, undefined, true);


// This creates and positions a free camera (non-mesh)
scene.createDefaultCamera(true, true, true);
scene.activeCamera.alpha += Math.PI;
Expand Down
4 changes: 4 additions & 0 deletions Polyfills/Canvas/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ set(SOURCES
"Source/Canvas.cpp"
"Source/Canvas.h"
"Source/Colors.h"
"Source/FrameBufferPool.cpp"
"Source/FrameBufferPool.h"
"Source/Image.cpp"
"Source/Image.h"
"Source/ImageData.cpp"
Expand All @@ -115,6 +117,8 @@ set(SOURCES
"Source/nanovg/nanovg.h"
"Source/nanovg/nanovg_babylon.cpp"
"Source/nanovg/nanovg_babylon.h"
"Source/nanovg/nanovg_filterstack.cpp"
"Source/nanovg/nanovg_filterstack.h"
)

file(GLOB SHADERS "Source/Shaders/*.sc" "Source/Shaders/*.sh")
Expand Down
29 changes: 20 additions & 9 deletions Polyfills/Canvas/Source/Canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,27 @@ namespace Babylon::Polyfills::Internal
std::array<bgfx::Attachment, textures.size()> attachments{};
for (size_t idx = 0; idx < attachments.size(); ++idx)
{
attachments[idx].init(textures[idx]);
std::array<bgfx::TextureHandle, 1> textures{
bgfx::createTexture2D(m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT) };

std::array<bgfx::Attachment, textures.size()> attachments{};
for (size_t idx = 0; idx < attachments.size(); ++idx)
{
attachments[idx].init(textures[idx]);
}
auto handle = bgfx::createFrameBuffer(static_cast<uint8_t>(attachments.size()), attachments.data(), true);
assert(handle.idx != bgfx::kInvalidHandle);
m_frameBuffer = std::make_unique<Graphics::FrameBuffer>(m_graphicsContext, handle, m_width, m_height, false, false, false);
m_dirty = false;

if (m_texture)
{
m_texture.reset();
}
}
auto handle = bgfx::createFrameBuffer(static_cast<uint8_t>(attachments.size()), attachments.data(), true);
assert(handle.idx != bgfx::kInvalidHandle);
m_frameBuffer = std::make_unique<Graphics::FrameBuffer>(m_graphicsContext, handle, m_width, m_height, false, false, false);
m_dirty = false;

if (m_texture)
{
m_texture.reset();
}
m_frameBufferPool.Clear();
m_frameBufferPool.SetGraphicsContext(&m_graphicsContext);

return true;
}
Expand Down Expand Up @@ -200,6 +210,7 @@ namespace Babylon::Polyfills::Internal
{
m_frameBuffer.reset();
m_texture.reset();
m_frameBufferPool.Clear();
}

void NativeCanvas::Dispose(const Napi::CallbackInfo& /*info*/)
Expand Down
3 changes: 3 additions & 0 deletions Polyfills/Canvas/Source/Canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <Babylon/Graphics/FrameBuffer.h>
#include <Babylon/Graphics/Texture.h>

#include "FrameBufferPool.h"

namespace Babylon::Polyfills
{
class Canvas::Impl final : public std::enable_shared_from_this<Canvas::Impl>
Expand Down Expand Up @@ -67,6 +69,7 @@ namespace Babylon::Polyfills::Internal
// returns true if frameBuffer size has changed
bool UpdateRenderTarget();
Babylon::Graphics::FrameBuffer& GetFrameBuffer() { return *m_frameBuffer; }
FrameBufferPool m_frameBufferPool;

Graphics::DeviceContext& GetGraphicsContext()
{
Expand Down
68 changes: 67 additions & 1 deletion Polyfills/Canvas/Source/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ namespace Babylon::Polyfills::Internal
InstanceAccessor("lineCap", &Context::GetLineCap, &Context::SetLineCap),
InstanceAccessor("lineJoin", &Context::GetLineJoin, &Context::SetLineJoin),
InstanceAccessor("miterLimit", &Context::GetMiterLimit, &Context::SetMiterLimit),
InstanceAccessor("filter", &Context::GetFilter, &Context::SetFilter),
InstanceAccessor("font", &Context::GetFont, &Context::SetFont),
InstanceAccessor("letterSpacing", &Context::GetLetterSpacing, &Context::SetLetterSpacing),
InstanceAccessor("strokeStyle", &Context::GetStrokeStyle, &Context::SetStrokeStyle),
Expand Down Expand Up @@ -173,6 +174,16 @@ namespace Babylon::Polyfills::Internal
}
}

void Context::SetFilterStack()
{
if (m_filter.length())
{
nanovg_filterstack filterStack;
filterStack.ParseString(m_filter);
nvgFilterStack(*m_nvg, filterStack); // sets filterStack on nanovg
}
}

void Context::FillRect(const Napi::CallbackInfo& info)
{
auto left = info[0].As<Napi::Number>().FloatValue();
Expand All @@ -188,7 +199,8 @@ namespace Babylon::Polyfills::Internal
nvgRect(*m_nvg, left, top, width, height);

BindFillStyle(info, left, top, width, height);


SetFilterStack();
nvgFill(*m_nvg);
SetDirty();
}
Expand Down Expand Up @@ -249,6 +261,8 @@ namespace Babylon::Polyfills::Internal

void Context::Fill(const Napi::CallbackInfo& info)
{
SetFilterStack();

const NativeCanvasPath2D* path = info.Length() >= 1 && info[0].IsObject()
? NativeCanvasPath2D::Unwrap(info[0].As<Napi::Object>())
: nullptr;
Expand Down Expand Up @@ -429,6 +443,7 @@ namespace Babylon::Polyfills::Internal
const auto height = info[3].As<Napi::Number>().FloatValue();

nvgRect(*m_nvg, left, top, width, height);
SetFilterStack();
nvgStroke(*m_nvg);
SetDirty();
}
Expand Down Expand Up @@ -505,6 +520,7 @@ namespace Babylon::Polyfills::Internal
SetDirty();
}

SetFilterStack();
nvgStroke(*m_nvg);
SetDirty();
}
Expand Down Expand Up @@ -571,6 +587,13 @@ namespace Babylon::Polyfills::Internal
{
BindFillStyle(info, 0.f, 0.f, x, y);

if (m_filter.length())
{
nanovg_filterstack filterStack;
filterStack.ParseString(m_filter);
nvgFilterStack(*m_nvg, filterStack); // sets filterStack on nanovg
}

nvgText(*m_nvg, x, y, text.c_str(), nullptr);
SetDirty();
}
Expand Down Expand Up @@ -605,10 +628,35 @@ namespace Babylon::Polyfills::Internal
const auto width = m_canvas->GetWidth();
const auto height = m_canvas->GetHeight();

for (auto& buffer : m_canvas->m_frameBufferPool.GetPoolBuffers())
{
// sanity check no buffers should have been acquired yet
assert(buffer.isAvailable == true);
}
std::function<Babylon::Graphics::FrameBuffer*()> acquire = [this, encoder]() -> Babylon::Graphics::FrameBuffer* {
Babylon::Graphics::FrameBuffer *frameBuffer = this->m_canvas->m_frameBufferPool.Acquire();
frameBuffer->Bind(*encoder);
return frameBuffer;
};
std::function<void(Babylon::Graphics::FrameBuffer*)> release = [this, encoder](Babylon::Graphics::FrameBuffer* frameBuffer) -> void {
// clear framebuffer when released
frameBuffer->Clear(*encoder, BGFX_CLEAR_COLOR, 0, 0, 0);
this->m_canvas->m_frameBufferPool.Release(frameBuffer);
frameBuffer->Unbind(*encoder);
};

nvgBeginFrame(*m_nvg, float(width), float(height), 1.0f);
nvgSetFrameBufferAndEncoder(*m_nvg, frameBuffer, encoder);
nvgSetFrameBufferPool(*m_nvg, { acquire, release });
nvgEndFrame(*m_nvg);
frameBuffer.Unbind(*encoder);

for (auto& buffer : m_canvas->m_frameBufferPool.GetPoolBuffers())
{
// sanity check no unreleased buffers
assert(buffer.isAvailable == true);
}

m_dirty = false;
}).then(arcana::inline_scheduler, *m_cancellationSource, [this, cancellationSource{m_cancellationSource}](const arcana::expected<void, std::exception_ptr>& result) {
if (!cancellationSource->cancelled() && result.has_error())
Expand Down Expand Up @@ -669,6 +717,7 @@ namespace Babylon::Polyfills::Internal

nvgRect(*m_nvg, dx, dy, width, height);
nvgFillPaint(*m_nvg, imagePaint);
SetFilterStack();
nvgFill(*m_nvg);
SetDirty();
}
Expand All @@ -688,6 +737,7 @@ namespace Babylon::Polyfills::Internal

nvgRect(*m_nvg, dx, dy, dWidth, dHeight);
nvgFillPaint(*m_nvg, imagePaint);
SetFilterStack();
nvgFill(*m_nvg);
SetDirty();
}
Expand All @@ -713,6 +763,7 @@ namespace Babylon::Polyfills::Internal

nvgRect(*m_nvg, dx, dy, dWidth, dHeight);
nvgFillPaint(*m_nvg, imagePaint);
SetFilterStack();
nvgFill(*m_nvg);
SetDirty();
}
Expand Down Expand Up @@ -853,6 +904,21 @@ namespace Babylon::Polyfills::Internal
SetDirty();
}

Napi::Value Context::GetFilter(const Napi::CallbackInfo& info)
{
return Napi::Value::From(Env(), m_filter);
}

void Context::SetFilter(const Napi::CallbackInfo& info, const Napi::Value& value)
{
std::string filterString = value.As<Napi::String>().Utf8Value();
// Keep existing filter if the new one is invalid
if (nanovg_filterstack::ValidString(filterString))
{
m_filter = filterString;
}
}

Napi::Value Context::GetFont(const Napi::CallbackInfo& info)
{
return Napi::Value::From(Env(), static_cast<std::string>(m_font));
Expand Down
5 changes: 5 additions & 0 deletions Polyfills/Canvas/Source/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "Image.h"
#include "Path2D.h"
#include "Font.h"
#include "nanovg/nanovg_filterstack.h"

struct NVGcontext;

Expand Down Expand Up @@ -68,6 +69,8 @@ namespace Babylon::Polyfills::Internal
void SetLineJoin(const Napi::CallbackInfo&, const Napi::Value& value);
Napi::Value GetMiterLimit(const Napi::CallbackInfo&);
void SetMiterLimit(const Napi::CallbackInfo&, const Napi::Value& value);
Napi::Value GetFilter(const Napi::CallbackInfo& info);
void SetFilter(const Napi::CallbackInfo& info, const Napi::Value& value);
Napi::Value GetFont(const Napi::CallbackInfo&);
void SetFont(const Napi::CallbackInfo&, const Napi::Value& value);
Napi::Value GetLetterSpacing(const Napi::CallbackInfo&);
Expand Down Expand Up @@ -97,6 +100,7 @@ namespace Babylon::Polyfills::Internal
std::string m_strokeStyle{};
std::string m_lineCap{}; // 'butt', 'round', 'square'
std::string m_lineJoin{}; // 'round', 'bevel', 'miter'
std::string m_filter{};
float m_miterLimit{0.f};
float m_lineWidth{0.f};
float m_globalAlpha{1.f};
Expand All @@ -123,6 +127,7 @@ namespace Babylon::Polyfills::Internal
void BindFillStyle(const Napi::CallbackInfo& info, float left, float top, float width, float height);
void FlushGraphicResources() override;
void PlayPath2D(const NativeCanvasPath2D* path);
void SetFilterStack();

friend class Canvas;
};
Expand Down
105 changes: 105 additions & 0 deletions Polyfills/Canvas/Source/FrameBufferPool.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include <array>
#include <vector>
#include <stdexcept>

#include <bimg/bimg.h>
#include <bgfx/bgfx.h>
#include "FrameBufferPool.h"

namespace Babylon::Polyfills
{
const std::vector<FrameBufferPool::PoolBuffer>& FrameBufferPool::GetPoolBuffers()
{
return mPoolBuffers;
}

// sets graphics context to be used for creating framebuffers
void FrameBufferPool::SetGraphicsContext(Graphics::DeviceContext* graphicsContext)
{
m_graphicsContext = graphicsContext;
}

void FrameBufferPool::Add(int nBuffers)
{
if (m_graphicsContext == nullptr)
{
throw std::runtime_error("Cannot add framebuffer to pool. Graphics context is not set.");
}

for (int i = 0; i < nBuffers; ++i)
{
bgfx::FrameBufferHandle TextBuffer{bgfx::kInvalidHandle};
Graphics::FrameBuffer* FrameBuffer;
int width(256), height(256);

// make sure render targets are filled with 0 : https://registry.khronos.org/webgl/specs/latest/1.0/#TEXIMAGE2D
bgfx::ReleaseFn releaseFn{[](void*, void* userData) {
bimg::imageFree(static_cast<bimg::ImageContainer*>(userData));
}};

bimg::ImageContainer* image = bimg::imageAlloc(&Babylon::Graphics::DeviceContext::GetDefaultAllocator(), bimg::TextureFormat::RGBA8, width, height, 1 /*depth*/, 1, false /*cubeMap*/, false /*hasMips*/);
const bgfx::Memory* mem = bgfx::makeRef(image->m_data, image->m_size, releaseFn, image);
bx::memSet(image->m_data, 0, image->m_size);

std::array<bgfx::TextureHandle, 1> textures{
bgfx::createTexture2D(width, height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT, mem)};

std::array<bgfx::Attachment, textures.size()> attachments{};
for (size_t idx = 0; idx < attachments.size(); ++idx)
{
attachments[idx].init(textures[idx]);
}
TextBuffer = bgfx::createFrameBuffer(static_cast<uint8_t>(attachments.size()), attachments.data(), true);

FrameBuffer = new Graphics::FrameBuffer(*m_graphicsContext, TextBuffer, width, height, false, false, false);
m_available++;
mPoolBuffers.push_back({FrameBuffer, true});
}
}

void FrameBufferPool::Clear()
{
for (auto& buffer : mPoolBuffers)
{
if (buffer.frameBuffer)
{
buffer.frameBuffer->Dispose();
}
}
m_available = 0;
mPoolBuffers.clear();
}

Graphics::FrameBuffer* FrameBufferPool::Acquire()
{
// no buffers in pool, add one
if (m_available == 0)
{
Add(1);
}

for (auto& buffer : mPoolBuffers)
{
if (buffer.isAvailable)
{
buffer.isAvailable = false;
m_available--;
return buffer.frameBuffer;
}
}

throw std::runtime_error("No available frame buffer in pool.");
}

void FrameBufferPool::Release(Graphics::FrameBuffer* frameBuffer)
{
for (auto& buffer : mPoolBuffers)
{
if (buffer.frameBuffer == frameBuffer)
{
buffer.isAvailable = true;
return;
}
}
}
}
Loading