Skip to content

Canvas test #1468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 41 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0418e9f
Canvas Context angle/fontsize fixes
CedricGuillemet Feb 6, 2025
bbdcce3
Text/shape/state test script
CedricGuillemet Feb 6, 2025
e0f88f3
readme update
CedricGuillemet Feb 6, 2025
57ec8d7
Merge branch 'master' into CanvasTest
ryantrem Feb 7, 2025
a9737fc
Canvas transform implementation (#1460)
mattbirman Feb 17, 2025
dadeca3
plumb lineCap, lineJoin, miterLimit to nanovg
Pheo Feb 18, 2025
975e0dd
Add textMetrics implementation (#1461)
mattbirman Feb 18, 2025
7bbca53
Add letterSpacing polyfill and implementation (#1462)
mattbirman Feb 18, 2025
ef0b545
Merge branch 'master' of https://github.com/BabylonJS/BabylonNative i…
CedricGuillemet Feb 19, 2025
b733ab3
Canvas test update (#1463)
CedricGuillemet Feb 19, 2025
bbe83fa
map strings to lineCaps
Pheo Feb 19, 2025
c9be817
Merge branch 'CanvasLineImplementation' of https://github.com/Pheo/Ba…
CedricGuillemet Feb 20, 2025
1538543
Add lineCap, lineJoin, miterLimit (#1464)
Pheo Feb 20, 2025
aab744e
Gradient color stop (#1467)
CedricGuillemet Feb 25, 2025
d599479
Merge branch 'CanvasTest' of https://github.com/BabylonJS/BabylonNati…
CedricGuillemet Feb 25, 2025
4d03c02
fix merge conflicts
CedricGuillemet Feb 25, 2025
e404270
JSI build
CedricGuillemet Feb 25, 2025
73693f9
Path2D Implementation (#1469)
Pheo Feb 28, 2025
146210b
Merge branch 'master' of https://github.com/BabylonJS/BabylonNative i…
CedricGuillemet Mar 4, 2025
3235ff7
Canvas Transforms: getTransform setTransform, addPath (Path2D) (#1473)
Pheo Mar 4, 2025
4c60d74
Merge branch 'master' of https://github.com/BabylonJS/BabylonNative i…
CedricGuillemet Mar 5, 2025
a4a0082
Merge branch 'CanvasTest' of https://github.com/BabylonJS/BabylonNati…
CedricGuillemet Mar 5, 2025
de71606
Implement round rect (#1471)
hwarmington Mar 11, 2025
7cb49ba
Copy nanovg into Canvas sources (#1480)
matanui159 Mar 13, 2025
e154eb9
fixed MSVC build
CedricGuillemet Mar 13, 2025
c914be2
Use bgfx more recent glslang/spirv for NativeEngine (#1478)
CedricGuillemet Mar 13, 2025
2c1dcfe
fix mac build
CedricGuillemet Mar 13, 2025
f7a563d
Canvas gradient text (#1475)
CedricGuillemet Mar 14, 2025
58d3b2f
Add strokeText support to canvas via SDFs (#1482)
matanui159 Mar 18, 2025
6bf07b3
canvas fill Path2D (#1483)
Pheo Mar 24, 2025
5ef4c23
Canvas and Context (#1491)
CedricGuillemet Apr 4, 2025
cfe7305
Merge branch 'master' of https://github.com/BabylonJS/BabylonNative i…
CedricGuillemet Apr 7, 2025
c2d4a22
fixes: canvas.SetHeight/Width, Path2D, context.fill (#1493)
Pheo Apr 7, 2025
dfb0a8b
Improve parsing of the "font" field (#1486)
matanui159 Apr 7, 2025
f222e21
nanovg Compositor + context.filter blur (#1489)
Pheo Apr 7, 2025
7de5058
missing vector include
CedricGuillemet Apr 7, 2025
ff7c6e6
Use property for getting canvas from context (#1495)
CedricGuillemet Apr 7, 2025
207aa2e
work around for jsi
CedricGuillemet Apr 8, 2025
12ee2b3
fix: pool framebuffer stencil, release issues (#1496)
Pheo Apr 8, 2025
5abadc2
Canvas Path2D roundRect (#1501)
Pheo Apr 17, 2025
c6f22a8
Canvas direction + getTransform() fix (#1502)
Pheo Apr 17, 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
1 change: 0 additions & 1 deletion .github/jobs/test_install_win32.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:

# BGFX_CONFIG_MAX_FRAME_BUFFERS is set so enough Framebuffers are available before V8 starts disposing unused ones
- script: |
# BGFX_CONFIG_MAX_FRAME_BUFFERS is set so enough Framebuffers are available before V8 starts disposing unused ones
cmake -B build${{ variables.solutionName }} -A ${{ parameters.platform }} ${{ variables.jsEngineDefine }} -D BX_CONFIG_DEBUG=ON -D GRAPHICS_API=${{ parameters.graphics_api }} -D CMAKE_UNITY_BUILD=$(UNITY_BUILD) -D BGFX_CONFIG_MAX_FRAME_BUFFERS=256 -D BABYLON_DEBUG_TRACE=ON
displayName: 'Generate ${{ variables.solutionName }} solution'

Expand Down
198 changes: 197 additions & 1 deletion Apps/Playground/Scripts/experience.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const imageTracking = false;
const readPixels = false;

function CreateBoxAsync(scene) {
BABYLON.Mesh.CreateBox("box1", 0.2, scene);
//BABYLON.Mesh.CreateBox("box1", 0.2, scene);
return Promise.resolve();
}

Expand Down Expand Up @@ -64,6 +64,202 @@ CreateBoxAsync(scene).then(function () {
//BABYLON.SceneLoader.AppendAsync("https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/ClearCoatTest/glTF/ClearCoatTest.gltf").then(function () {
BABYLON.Tools.Log("Loaded");

var textureGround;

BABYLON.Tools.LoadFile("https://raw.githubusercontent.com/CedricGuillemet/dump/master/droidsans.ttf", (data) => {
_native.Canvas.loadTTFAsync("droidsans", data).then(function () {
var ground = BABYLON.MeshBuilder.CreateGround("ground1", { width: 0.5, height: 0.5, subdivisions: 2 }, scene);
ground.rotation.x = -Math.PI * 0.5;
ground.rotation.y = Math.PI;

var texSize = 512;
var dynamicTexture = new BABYLON.DynamicTexture("dynamic texture", texSize, scene);

var materialGround = new BABYLON.StandardMaterial("Mat", scene);
materialGround.diffuseTexture = dynamicTexture;
ground.material = materialGround;
materialGround.backFaceCulling = false;
dynamicTexture.clear();
var context = dynamicTexture.getContext();

// Text with gradient
const gradientText = context.createLinearGradient(0, 0, 256, 0);
gradientText.addColorStop(0, "magenta");
gradientText.addColorStop(0.5, "blue");
gradientText.addColorStop(1.0, "red");

// gradient
let gradient = context.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, "green");
gradient.addColorStop(0.7, "white");
gradient.addColorStop(1, "pink");

var t = 0;
scene.onBeforeRenderObservable.add(() => {
// animated shape
context.save();
context.fillStyle = "DarkRed";
context.fillRect(0, 0, texSize, texSize);
const left = 0;
const top = texSize - (texSize * 0.25);
const width = 0.25 * texSize;
const height = 0.25 * texSize;
const offsetU = ((Math.sin(t) * 0.5) + 0.5) * (texSize - (texSize * 0.25));
const offsetV = ((Math.sin(t) * 0.5) + 0.5) * (-texSize + (texSize * 0.25));
const rectangleU = width * 0.5 + left;
const rectangleV = height * 0.5 + top;
context.translate(rectangleU + offsetU, rectangleV + offsetV);
context.rotate(t);
context.fillStyle = "DarkOrange";
context.transform(1, t, 0.8, 1, 0, 0);
context.fillRect(-width * 0.5, -height * 0.5, width, height);
context.restore();

// curve
context.beginPath();
context.moveTo(75 * 2, 25 * 2);
context.quadraticCurveTo(25 * 2, 25 * 2, 25 * 2, 62.5 * 2);
context.quadraticCurveTo(25 * 2, 100 * 2, 50 * 2, 100 * 2);
context.quadraticCurveTo(50 * 2, 120 * 2, 30 * 2, 125 * 2);
context.quadraticCurveTo(60 * 2, 120 * 2, 65 * 2, 100 * 2);
context.quadraticCurveTo(125 * 2, 100 * 2, 125 * 2, 62.5 * 2);
context.quadraticCurveTo(125 * 2, 25 * 2, 75 * 2, 25 * 2);
context.fillStyle = "blue";
context.fill();

// text
var scale = Math.sin(t) * 0.5 + 0.54;
context.save();
context.translate(Math.cos(t) * 100, 246);
context.font = `bold ${scale * 200}px monospace`;
context.strokeStyle = "Green";
context.lineWidth = scale * 16;
context.strokeText("BabylonNative", 0, 0);
context.fillStyle = "White";
context.fillText("BabylonNative", 0, 0);
context.restore();

// Draw guides
context.strokeStyle = "#09f";
context.beginPath();
context.moveTo(10, 10);
context.lineTo(140, 10);
context.moveTo(10, 140);
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) => {
context.lineWidth = 15;
context.lineCap = lineCap;
context.beginPath();
context.moveTo(25 + i * 50, 10);
context.lineTo(25 + i * 50, 140);
context.stroke();
});

// line join
context.lineWidth = 10;
var offset = 200;
["round", "bevel", "miter"].forEach((join, i) => {
context.lineJoin = join;
context.beginPath();
context.moveTo(-5 + offset, 15 + i * 40);
context.lineTo(35 + offset, 55 + i * 40);
context.lineTo(75 + offset, 15 + i * 40);
context.lineTo(115 + offset, 55 + i * 40);
context.lineTo(155 + offset, 15 + i * 40);
context.stroke();
});

// rect with gradient
context.fillStyle = gradient;
context.fillRect(10, 310, 400, 60);

// Fill with gradient
context.fillStyle = gradientText;
context.font = "bold 60px monospace";
context.fillText("Gradient Text!", 10, 420);

context.lineWidth = 5;
// Rounded rectangle with zero radius (specified as a number)
context.strokeStyle = "red";
context.beginPath();
context.roundRect(10, 220, 150, 100, 0);
context.stroke();

// Rounded rectangle with 40px radius (single element list)
context.strokeStyle = "blue";
context.beginPath();
context.roundRect(10, 220, 150, 100, [40]);
context.stroke();

// Rounded rectangle with 2 different radii
context.strokeStyle = "orange";
context.beginPath();
context.roundRect(10, 350, 150, 100, [10, 40]);
context.stroke();

// Rounded rectangle with four different radii
context.strokeStyle = "green";
context.beginPath();
context.roundRect(200, 220, 200, 100, [0, 30, 50, 60]);
context.stroke();

// Same rectangle drawn backwards
context.strokeStyle = "magenta";
context.beginPath();
context.roundRect(400, 350, -200, 100, [0, 30, 50, 60]);
context.stroke();

// Path 2D stroke
context.strokeStyle = "black";
context.lineWidth = 2;
let heartPath = new engine.createCanvasPath2D("M390,30 A 20, 20 0, 0, 1 430, 30 A 20, 20 0, 0, 1 470, 30 Q 470, 60 430, 90 Q 390, 60 390, 30 z");
let squarePath = new engine.createCanvasPath2D("M380, 10 h100 v100 h-100 Z");
heartPath.addPath(squarePath, { a: 1, b: 0, c: 0, d: 1, e: 0, f: -5 }); // push square 5px up to center heart.
context.stroke(heartPath);

// Path 2D fill
context.fillStyle = "yellow";
let diamondPath = new engine.createCanvasPath2D();
diamondPath.moveTo(350, 200); // Start at the center
diamondPath.lineTo(375, 175); // Move to the top point
diamondPath.lineTo(400, 200); // Move to the right point
diamondPath.lineTo(375, 225); // Move to the bottom point
diamondPath.lineTo(350, 200); // Close back to the starting point
context.fill(diamondPath);

// Path 2D round rect
context.strokeStyle = "red";
let roundRectPath = new engine.createCanvasPath2D();
roundRectPath.roundRect(300, 150, 45, 70, [10, 35]);
context.stroke(roundRectPath);

// Draw clipped round rect
// TODO: this is currently broken, clipping area does not have round corners
context.beginPath();
context.roundRect(40, 450, 100, 50, 10);
context.clip();
context.fillStyle = "blue";
context.fillRect(0, 0, 1000, 1000);

// tick update
dynamicTexture.update();
t += 0.01;
});

});
}, undefined, undefined, true);

// This creates and positions a free camera (non-mesh)
scene.createDefaultCamera(true, true, true);
scene.activeCamera.alpha += Math.PI;
Expand Down
17 changes: 16 additions & 1 deletion Apps/UnitTests/Scripts/tests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mocha.setup({ ui: "bdd", reporter: "spec", retries: 5 });
"use strict";
mocha.setup({ ui: "bdd", reporter: "spec", retries: 5 });

const expect = chai.expect;

Expand All @@ -15,6 +16,20 @@ describe("RequestFile", function () {
});
});

describe("CanvasAndContext", function () {
const engine = new BABYLON.NativeEngine();
const scene = new BABYLON.Scene(engine);

const texSize = 512;
const dynamicTexture = new BABYLON.DynamicTexture("dynamic texture", texSize, scene);
const context = dynamicTexture.getContext();
const otherContext = dynamicTexture.getContext();

expect(context).to.equal(context.canvas.getContext());
expect(context).to.equal(otherContext);
expect(context).to.equal(otherContext.canvas.getContext());
});

describe("ColorParsing", function () {
expect(_native.Canvas.parseColor("")).to.equal(0);
expect(_native.Canvas.parseColor("transparent")).to.equal(0);
Expand Down
49 changes: 34 additions & 15 deletions Plugins/NativeEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,40 @@ target_include_directories(NativeEngine
PUBLIC "Include"
PRIVATE "${BIMG_DIR}/3rdparty")

target_link_libraries(NativeEngine
PUBLIC napi
PRIVATE JsRuntime
PRIVATE GraphicsDevice
PRIVATE arcana
PRIVATE bgfx
PRIVATE bimg
PRIVATE bimg_encode
PRIVATE bimg_decode
PRIVATE bx
PRIVATE glslang
PRIVATE glslang-default-resource-limits
PRIVATE SPIRV
PRIVATE GraphicsDeviceContext)
warnings_as_errors(NativeEngine)

if(BGFX_BUILD_TOOLS)
target_link_libraries(NativeEngine
PUBLIC napi
PRIVATE JsRuntime
PRIVATE GraphicsDevice
PRIVATE arcana
PRIVATE bgfx
PRIVATE bimg
PRIVATE bimg_encode
PRIVATE bimg_decode
PRIVATE bx
PRIVATE glslang
PRIVATE spirv-opt
PRIVATE GraphicsDeviceContext)
target_compile_definitions(NativeEngine PRIVATE BGFX_BUILD_TOOLS)
else()
target_link_libraries(NativeEngine
PUBLIC napi
PRIVATE JsRuntime
PRIVATE GraphicsDevice
PRIVATE arcana
PRIVATE bgfx
PRIVATE bimg
PRIVATE bimg_encode
PRIVATE bimg_decode
PRIVATE bx
PRIVATE glslang
PRIVATE glslang-default-resource-limits
PRIVATE SPIRV
PRIVATE GraphicsDeviceContext)

warnings_as_errors(NativeEngine)
endif()

if(TARGET spirv-cross-hlsl)
target_link_libraries(NativeEngine
Expand Down
1 change: 1 addition & 0 deletions Plugins/NativeEngine/Source/ShaderCompilerMetal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <bgfx/bgfx.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/Public/ResourceLimits.h>
#include <glslang/MachineIndependent/localintermediate.h>
#include <SPIRV/GlslangToSpv.h>
#include <spirv_parser.hpp>
#include <spirv_msl.hpp>
Expand Down
25 changes: 20 additions & 5 deletions Plugins/NativeEngine/Source/ShaderCompilerTraversers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,11 @@ namespace Babylon::ShaderCompilerTraversers
// Create the symbol for the actual struct. The name of this symbol, "anon@0",
// mirrors the kinds of strings glslang generates automatically for these sorts
// of objects.
TIntermSymbol* structSymbol = intermediate->addSymbol(TIntermSymbol{ids.Next(), "anon@0", structType});

#ifdef BGFX_BUILD_TOOLS
TIntermSymbol* structSymbol = intermediate->addSymbol(TIntermSymbol{ids.Next(), "anon@0", intermediate->getStage(), structType});
#else
TIntermSymbol* structSymbol = intermediate->addSymbol(TIntermSymbol{ ids.Next(), "anon@0", structType });
#endif
// Every affected symbol in the AST (except linker objects) must be replaced
// with a new operation to retrieve its value from the struct. This operation
// consists of a binary operation indexing into the struct at a specified
Expand Down Expand Up @@ -521,7 +524,11 @@ namespace Babylon::ShaderCompilerTraversers

TType newType{publicType};
newType.setBasicType(symbol->getType().getBasicType());
auto* newSymbol = intermediate->addSymbol(TIntermSymbol{ids.Next(), newName, newType});
#ifdef BGFX_BUILD_TOOLS
auto* newSymbol = intermediate->addSymbol(TIntermSymbol{ids.Next(), newName, intermediate->getStage(), newType});
#else
auto* newSymbol = intermediate->addSymbol(TIntermSymbol{ ids.Next(), newName, newType });
#endif
originalNameToReplacement[name] = newSymbol;
replacementToOriginalName[newName] = name;
}
Expand Down Expand Up @@ -794,7 +801,11 @@ namespace Babylon::ShaderCompilerTraversers

TType newType{publicType};
std::string newName = name + "Texture";
newTexture = intermediate->addSymbol(TIntermSymbol{ids.Next(), newName.c_str(), newType});
#ifdef BGFX_BUILD_TOOLS
newTexture = intermediate->addSymbol(TIntermSymbol{ids.Next(), newName.c_str(), intermediate->getStage(), newType});
#else
newTexture = intermediate->addSymbol(TIntermSymbol{ ids.Next(), newName.c_str(), newType });
#endif
}

// Create the new sampler symbol.
Expand All @@ -809,7 +820,11 @@ namespace Babylon::ShaderCompilerTraversers
publicType.sampler.sampler = true;

TType newType{publicType};
newSampler = intermediate->addSymbol(TIntermSymbol{ids.Next(), name.c_str(), newType});
#ifdef BGFX_BUILD_TOOLS
newSampler = intermediate->addSymbol(TIntermSymbol{ids.Next(), name.c_str(), intermediate->getStage(), newType});
#else
newSampler = intermediate->addSymbol(TIntermSymbol{ ids.Next(), name.c_str(), newType });
#endif
}

nameToNewTextureAndSampler[name] = std::pair<TIntermSymbol*, TIntermSymbol*>{newTexture, newSampler};
Expand Down
Loading
Loading