From 42c8c0bfb910cc0c9e0a99728daafbab15d5c853 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 15:31:00 -0700 Subject: [PATCH 01/16] Honor attribute-less instance counts Submit the requested instance count when a draw derives its per-instance data from gl_InstanceID instead of an instance buffer. Map vertex/instance built-ins to Vulkan's index built-ins and assign matching shader interface locations, including explicitly reserved locations. Add compiler and rendering regressions for these paths. Run the indexed and unindexed rendering regression on every real renderer, including D3D12 and installed-SDK consumers. Skip it only when the test build uses a no-op Metal device. Review-Group: E1 Source: PR #1888 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: a79dba316c9e38f9597fa4bf16f652551a20a864 Split-Part: code --- Apps/UnitTests/CMakeLists.txt | 1 + .../JavaScript/src/tests.javaScript.all.ts | 3 + ...ts.nativeEngine.attributeLessInstancing.ts | 82 +++++++ Apps/UnitTests/Source/Tests.JavaScript.cpp | 5 + .../Source/Tests.ShaderCompilation.cpp | 218 ++++++++++++++++++ Plugins/NativeEngine/Source/VertexArray.cpp | 5 + .../Source/ShaderCompilerVulkan.cpp | 110 +++++++++ 7 files changed, 424 insertions(+) create mode 100644 Apps/UnitTests/JavaScript/src/tests.nativeEngine.attributeLessInstancing.ts diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index da7b7b23f..685fa192c 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -152,6 +152,7 @@ if(GRAPHICS_API STREQUAL "D3D12") endif() if(GRAPHICS_API STREQUAL "Vulkan") + target_link_libraries(UnitTests PRIVATE ShaderCompilerInternal spirv-cross-core glslang) # Vulkan packaging paths (e.g. the >16 sampler rejection) are gated on this define. # ExternalTexture / native-texture helpers are not implemented for Vulkan yet, so skip # those tests rather than failing at link or runtime. diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index d861bf035..fe267ba3e 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -24,6 +24,7 @@ import { } from "@babylonjs/core"; import { GradientMaterial } from "@babylonjs/materials"; import { registerPngTests } from "./tests.nativeEngine.png"; +import { registerAttributeLessInstancingTests } from "./tests.nativeEngine.attributeLessInstancing"; declare var describe: typeof Mocha.describe; declare var it: typeof Mocha.it; @@ -35,12 +36,14 @@ Mocha.reporter("spec"); declare const hostPlatform: string; declare const hasGpuRendering: boolean; declare const hasNativeImageLoading: boolean; +declare const hasAttributeLessInstancing: boolean; declare const setExitCode: (code: number) => void; declare const setImageReloadTestResponse: (bytes: Uint8Array) => void; declare const skipCanvasGpuTests: boolean; declare const _native: any; registerPngTests(describe, it, hasGpuRendering && hasNativeImageLoading); +registerAttributeLessInstancingTests(describe, it, hasAttributeLessInstancing); describe("Native texture readback", function () { this.timeout(10000); diff --git a/Apps/UnitTests/JavaScript/src/tests.nativeEngine.attributeLessInstancing.ts b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.attributeLessInstancing.ts new file mode 100644 index 000000000..836e7cc58 --- /dev/null +++ b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.attributeLessInstancing.ts @@ -0,0 +1,82 @@ +import * as Mocha from "mocha"; +import { expect } from "chai"; +import { + Color4, FreeCamera, MeshBuilder, NativeEngine, RenderTargetTexture, + Scene, ShaderMaterial, Vector3 +} from "@babylonjs/core"; + +export function registerAttributeLessInstancingTests( + describe: typeof Mocha.describe, + it: typeof Mocha.it, + enabled: boolean +): void { + describe("NativeEngine attribute-less instancing", function () { + (enabled ? it : it.skip)("draws every instance without an instance buffer", async function () { + this.timeout(15000); + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + scene.clearColor = new Color4(0, 0, 0, 1); + const camera = new FreeCamera("camera", new Vector3(0, 0, -5), scene); + camera.setTarget(Vector3.Zero()); + const material = new ShaderMaterial("instances", scene, { + vertexSource: ` + precision highp float; + attribute vec3 position; + varying vec3 instanceColor; + void main() { + float offset = (float(gl_InstanceID) - 1.0) * 0.6; + gl_Position = vec4(position.x * 0.2 + offset, position.y * 0.2, 0.5, 1.0); + instanceColor = vec3(float(gl_InstanceID == 0), float(gl_InstanceID == 1), float(gl_InstanceID == 2)); + }`, + fragmentSource: ` + precision highp float; + varying vec3 instanceColor; + void main() { gl_FragColor = vec4(instanceColor, 1.0); }` + }, { attributes: ["position"], uniforms: [] }); + material.backFaceCulling = false; + const target = new RenderTargetTexture("instances", 64, scene, false); + for (const unIndexed of [false, true]) { + const mesh = MeshBuilder.CreatePlane("plane", { size: 2 }, scene); + if (unIndexed) { + mesh.convertToUnIndexedMesh(); + } + mesh.material = material; + target.renderList = [mesh]; + for (const count of [0, 1, 3]) { + mesh.forcedInstanceCount = count; + await material.forceCompilationAsync(mesh, { useInstances: count > 0 }); + await scene.whenReadyAsync(); + const readinessDeadline = Date.now() + 10000; + while (!target.isReadyForRendering()) { + if (Date.now() >= readinessDeadline) { + throw new Error(`Render target did not become ready: unIndexed=${unIndexed}, count=${count}`); + } + await new Promise(resolve => setTimeout(resolve, 10)); + } + target.render(); + const pixels = await target.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 instance rendering readback"); + } + const expected = [ + [255, 0, 0, 255], + count === 3 ? [0, 255, 0, 255] : [0, 0, 0, 255], + count === 3 ? [0, 0, 255, 255] : [0, 0, 0, 255] + ]; + const centers = [13, 32, 51]; + const actual = centers.map(center => { + const offset = (32 * 64 + center) * 4; + return Array.from(pixels.subarray(offset, offset + 4)); + }); + expect(actual, `unIndexed=${unIndexed}, count=${count}`).to.deep.equal(expected); + } + mesh.dispose(); + } + } finally { + scene.dispose(); + engine.dispose(); + } + }); + }); +} diff --git a/Apps/UnitTests/Source/Tests.JavaScript.cpp b/Apps/UnitTests/Source/Tests.JavaScript.cpp index 2c48f6d36..1754e7748 100644 --- a/Apps/UnitTests/Source/Tests.JavaScript.cpp +++ b/Apps/UnitTests/Source/Tests.JavaScript.cpp @@ -153,6 +153,11 @@ TEST(JavaScript, All) #else env.Global().Set("hasNativeImageLoading", false); #endif +#ifdef USE_NOOP_METAL_DEVICE + env.Global().Set("hasAttributeLessInstancing", false); +#else + env.Global().Set("hasAttributeLessInstancing", true); +#endif #ifdef USE_NOOP_METAL_DEVICE env.Global().Set("hasGpuRendering", false); #else diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp index f65978ea2..dcc237fff 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp @@ -16,6 +16,13 @@ #include #include +#if defined(BABYLON_NATIVE_GRAPHICS_API_VULKAN) +#include +#include +#include +#include +#endif + using namespace std::chrono_literals; extern Babylon::Graphics::Configuration g_deviceConfig; @@ -319,6 +326,25 @@ TEST(ShaderCompilation, PartiallySharedStageSamplersCompile) #if defined(BABYLON_NATIVE_GRAPHICS_API_VULKAN) namespace { + spirv_cross::Compiler ReadVulkanShader(const std::vector& bytes) + { + // These fixtures have no uniforms: bgfx v12's code length starts at byte 22. + constexpr size_t codeOffset = 26; + if (bytes.size() < codeOffset || bytes[3] != 12 || bytes[20] != 0 || bytes[21] != 0) + { + throw std::runtime_error{"Expected a uniform-free bgfx v12 shader"}; + } + uint32_t codeSize{}; + std::memcpy(&codeSize, bytes.data() + codeOffset - sizeof(codeSize), sizeof(codeSize)); + if (codeSize % sizeof(uint32_t) != 0 || codeSize > bytes.size() - codeOffset) + { + throw std::runtime_error{"Invalid SPIR-V byte length"}; + } + std::vector words(codeSize / sizeof(uint32_t)); + std::memcpy(words.data(), bytes.data() + codeOffset, codeSize); + return spirv_cross::Compiler{std::move(words)}; + } + struct SamplerCompilationResult { bool IsReady; @@ -421,4 +447,196 @@ TEST(ShaderCompilation, VulkanRejectsMoreThanSixteenSamplers) EXPECT_FALSE(result.IsReady); EXPECT_NE(result.Error.find("Vulkan shader uses more than 16 distinct sampler textures"), std::string::npos) << result.Error; } + +TEST(ShaderCompilation, VulkanAssignsMatchingVaryingLocations) +{ + Babylon::Plugins::ShaderCompiler compiler; + for (unsigned explicitStages = 0; explicitStages < 4; ++explicitStages) + { + SCOPED_TRACE(explicitStages); + const std::string vertexLocation = (explicitStages & 1) ? "layout(location = 3) " : ""; + const std::string fragmentLocation = (explicitStages & 2) ? "layout(location = 3) " : ""; + const auto info = compiler.Compile( + "#version 310 es\nprecision highp float;\nin vec3 position;\n" + + vertexLocation + R"(out vec3 colorValue; + out vec2 extraValue; + void main() { + gl_Position = vec4(position, 1.0); + colorValue = position; + extraValue = position.xy; + })", + "#version 310 es\nprecision highp float;\nin vec2 extraValue;\n" + + fragmentLocation + R"(in vec3 colorValue; + layout(location = 0) out vec4 color; + void main() { color = vec4(colorValue.xy + extraValue, colorValue.z, 1.0); })"); + auto vertex = ReadVulkanShader(info.VertexBytes); + auto fragment = ReadVulkanShader(info.FragmentBytes); + const auto outputs = vertex.get_shader_resources().stage_outputs; + const auto inputs = fragment.get_shader_resources().stage_inputs; + ASSERT_EQ(outputs.size(), 2u); + ASSERT_EQ(inputs.size(), 2u); + EXPECT_NE(vertex.get_decoration(outputs[0].id, spv::DecorationLocation), + vertex.get_decoration(outputs[1].id, spv::DecorationLocation)); + for (const auto& output : outputs) + { + ASSERT_TRUE(vertex.has_decoration(output.id, spv::DecorationLocation)) << output.name; + bool matched{}; + for (const auto& input : inputs) + { + if (input.name == output.name) + { + ASSERT_TRUE(fragment.has_decoration(input.id, spv::DecorationLocation)) << input.name; + EXPECT_EQ(vertex.get_decoration(output.id, spv::DecorationLocation), + fragment.get_decoration(input.id, spv::DecorationLocation)); + if (explicitStages != 0 && output.name == "colorValue") + { + EXPECT_EQ(vertex.get_decoration(output.id, spv::DecorationLocation), 3u); + } + matched = true; + } + } + EXPECT_TRUE(matched) << output.name; + } + } +} + +TEST(ShaderCompilation, VulkanReservesMatrixArrayLocations) +{ + Babylon::Plugins::ShaderCompiler compiler; + const auto info = compiler.Compile( + R"(#version 310 es + precision highp float; + in vec3 position; + out mat2 matrixValue[2]; + out vec3 colorValue; + out vec2 vertexOnly; + void main() { + gl_Position = vec4(position, 1.0); + matrixValue[0] = mat2(1.0); + matrixValue[1] = mat2(2.0); + colorValue = position; + vertexOnly = position.xy; + })", + R"(#version 310 es + precision highp float; + layout(location = 3) in vec3 colorValue; + in mat2 matrixValue[2]; + out vec4 color; + void main() { color = vec4(matrixValue[0] * matrixValue[1] * colorValue.xy, colorValue.z, 1.0); })"); + auto vertex = ReadVulkanShader(info.VertexBytes); + auto fragment = ReadVulkanShader(info.FragmentBytes); + for (const auto* stage : {&vertex, &fragment}) + { + const auto resources = stage->get_shader_resources(); + const auto& varyings = stage == &vertex ? resources.stage_outputs : resources.stage_inputs; + ASSERT_EQ(varyings.size(), stage == &vertex ? 3u : 2u); + for (const auto& varying : varyings) + { + ASSERT_TRUE(stage->has_decoration(varying.id, spv::DecorationLocation)); + const auto location = stage->get_decoration(varying.id, spv::DecorationLocation); + if (varying.name == "matrixValue") + { + EXPECT_EQ(location, 4u); + } + else if (varying.name == "colorValue") + { + EXPECT_EQ(location, 3u); + } + else + { + EXPECT_EQ(varying.name, "vertexOnly"); + EXPECT_EQ(location, 0u); + } + } + } + const auto outputs = fragment.get_shader_resources().stage_outputs; + ASSERT_EQ(outputs.size(), 1u); + EXPECT_TRUE(fragment.has_decoration(outputs[0].id, spv::DecorationLocation)); + EXPECT_EQ(fragment.get_decoration(outputs[0].id, spv::DecorationLocation), 0u); + const auto inputs = vertex.get_shader_resources().stage_inputs; + ASSERT_EQ(inputs.size(), 1u); + EXPECT_EQ(vertex.get_decoration(inputs[0].id, spv::DecorationLocation), 0u); +} + +TEST(ShaderCompilation, VulkanLinksBlocksWithDifferentInstanceNames) +{ +#if defined(GLSLANG_WEB) + GTEST_SKIP() << "Interface blocks require BABYLON_NATIVE_DISABLE_WEBMIN"; +#else + Babylon::Plugins::ShaderCompiler compiler; + for (const std::string fragmentLocation : {"", "layout(location = 4) "}) + { + SCOPED_TRACE(fragmentLocation); + const auto info = compiler.Compile( + R"(#version 310 es + #extension GL_EXT_shader_io_blocks : require + precision highp float; + in vec3 position; + out Payload { mat2 transform; vec3 tint; } vertexData; + out vec2 extraValue; + void main() { + gl_Position = vec4(position, 1.0); + vertexData.transform = mat2(1.0); + vertexData.tint = position; + extraValue = position.xy; + })", + "#version 310 es\n#extension GL_EXT_shader_io_blocks : require\n" + "precision highp float;\nin vec2 extraValue;\n" + + fragmentLocation + R"(in Payload { mat2 transform; vec3 tint; } fragmentData; + layout(location = 0) out vec4 color; + void main() { color = vec4(fragmentData.transform * extraValue, fragmentData.tint.z, 1.0); })"); + auto vertex = ReadVulkanShader(info.VertexBytes); + auto fragment = ReadVulkanShader(info.FragmentBytes); + std::map vertexLocations; + std::map fragmentLocations; + for (const auto* stage : {&vertex, &fragment}) + { + const auto resources = stage->get_shader_resources(); + const auto& varyings = stage == &vertex ? resources.stage_outputs : resources.stage_inputs; + ASSERT_EQ(varyings.size(), 2u); + auto& locations = stage == &vertex ? vertexLocations : fragmentLocations; + for (const auto& varying : varyings) + { + ASSERT_TRUE(stage->has_decoration(varying.id, spv::DecorationLocation)); + locations[varying.name] = stage->get_decoration(varying.id, spv::DecorationLocation); + } + } + ASSERT_EQ(vertexLocations.count("Payload"), 1u); + ASSERT_EQ(vertexLocations.count("extraValue"), 1u); + EXPECT_EQ(vertexLocations, fragmentLocations); + const auto blockLocation = vertexLocations.at("Payload"); + const auto extraLocation = vertexLocations.at("extraValue"); + EXPECT_TRUE(extraLocation < blockLocation || extraLocation >= blockLocation + 3); + if (!fragmentLocation.empty()) + { + EXPECT_EQ(blockLocation, 4u); + } + } +#endif +} + +TEST(ShaderCompilation, VulkanUsesVertexAndInstanceIndexBuiltins) +{ + Babylon::Plugins::ShaderCompiler compiler; + const auto info = compiler.Compile( + R"(#version 310 es + precision highp float; + void main() { gl_Position = vec4(float(gl_VertexID), float(gl_InstanceID), 0.0, 1.0); })", + R"(#version 310 es + precision highp float; + layout(location = 0) out vec4 color; + void main() { color = vec4(1.0); })"); + auto vertex = ReadVulkanShader(info.VertexBytes); + bool vertexIndex{}; + bool instanceIndex{}; + for (const auto& input : vertex.get_shader_resources().builtin_inputs) + { + EXPECT_NE(input.builtin, spv::BuiltInVertexId); + EXPECT_NE(input.builtin, spv::BuiltInInstanceId); + vertexIndex |= input.builtin == spv::BuiltInVertexIndex; + instanceIndex |= input.builtin == spv::BuiltInInstanceIndex; + } + EXPECT_TRUE(vertexIndex); + EXPECT_TRUE(instanceIndex); +} #endif diff --git a/Plugins/NativeEngine/Source/VertexArray.cpp b/Plugins/NativeEngine/Source/VertexArray.cpp index 51bf12a10..67ee32d2f 100644 --- a/Plugins/NativeEngine/Source/VertexArray.cpp +++ b/Plugins/NativeEngine/Source/VertexArray.cpp @@ -140,6 +140,11 @@ namespace Babylon VertexBuffer::BuildInstanceDataBuffer(instanceDataBuffer, m_vertexBufferInstances, instanceCount, instanceDataLayout); encoder->setInstanceDataBuffer(&instanceDataBuffer); } + else if (instanceCount > 0) + { + // These draws derive per-instance data from gl_InstanceID instead of an instance buffer. + encoder->setInstanceCount(instanceCount); + } uint8_t stream = 0; for (const auto& pair : m_vertexBufferRecords) diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp index 4f519224c..c925f2a95 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp @@ -35,6 +35,102 @@ namespace // bgfx Vulkan m_bindInfo is indexed by sampler stage 0..15. constexpr unsigned kMaxBgfxTextureStages = 16; + class VulkanInterfaceLocationTraverser final : public TIntermTraverser + { + public: + void Collect(TIntermediate& intermediate, TStorageQualifier storage) + { + m_storage = storage; + intermediate.getTreeRoot()->traverse(this); + } + + void AssignLocations() + { + std::map locations; + std::set reserved; + for (const auto& [name, symbols] : m_symbols) + { + for (const auto* symbol : symbols) + { + const auto& qualifier = symbol->getQualifier(); + if (qualifier.hasLocation()) + { + locations[name] = qualifier.layoutLocation; + const auto count = TIntermediate::computeTypeLocationSize(symbol->getType(), EShLangFragment); + for (int offset = 0; offset < count; ++offset) + { + reserved.insert(qualifier.layoutLocation + offset); + } + } + } + } + for (const auto& [name, symbols] : m_symbols) + { + if (locations.find(name) == locations.end()) + { + const auto count = TIntermediate::computeTypeLocationSize(symbols.front()->getType(), EShLangFragment); + unsigned location{}; + for (int offset = 0; offset < count; ++offset) + { + if (reserved.count(location + offset)) + { + location += offset + 1; + offset = -1; + } + } + locations[name] = location; + for (int offset = 0; offset < count; ++offset) + { + reserved.insert(location + offset); + } + } + for (auto* symbol : symbols) + { + symbol->getWritableType().getQualifier().layoutLocation = locations.at(name); + } + } + } + + private: + void visitSymbol(TIntermSymbol* symbol) override + { + const auto& type = symbol->getType(); + if (type.getQualifier().storage != m_storage || type.isBuiltIn()) + { + return; + } + if (type.isStruct() && !type.getStruct()->empty() && type.getStruct()->front().type->isBuiltIn()) + { + return; + } + // Block instance names are stage-local; the block type identifies the interface. + const auto& name = type.getBasicType() == EbtBlock ? type.getTypeName() : symbol->getName(); + m_symbols[name.c_str()].push_back(symbol); + } + + TStorageQualifier m_storage{}; + std::map> m_symbols; + }; + + class VulkanBuiltInTraverser final : public TIntermTraverser + { + void visitSymbol(TIntermSymbol* symbol) override + { + auto& qualifier = symbol->getWritableType().getQualifier(); + switch (qualifier.builtIn) + { + case EbvVertexId: + qualifier.builtIn = EbvVertexIndex; + break; + case EbvInstanceId: + qualifier.builtIn = EbvInstanceIndex; + break; + default: + break; + } + } + }; + void CollectStageUniforms( glslang::TIntermediate* intermediate, std::vector& textures, @@ -277,6 +373,20 @@ namespace Babylon::Plugins throw std::runtime_error{program.getInfoLog()}; } + // The GLSLANG_WEB build omits mapIO. Match stage interfaces by name and + // reserve explicit locations before allocating implicit ones. + VulkanInterfaceLocationTraverser varyings; + varyings.Collect(*program.getIntermediate(EShLangVertex), EvqVaryingOut); + varyings.Collect(*program.getIntermediate(EShLangFragment), EvqVaryingIn); + varyings.AssignLocations(); + VulkanInterfaceLocationTraverser outputs; + outputs.Collect(*program.getIntermediate(EShLangFragment), EvqVaryingOut); + outputs.AssignLocations(); + + // Parse Babylon's ordinary GLSL, but emit Vulkan's index built-ins. + VulkanBuiltInTraverser builtIns; + program.getIntermediate(EShLangVertex)->getTreeRoot()->traverse(&builtIns); + ShaderCompilerTraversers::IdGenerator ids{}; // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). ShaderCompilerTraversers::FlipSamplerCoordinates(program); From 1de0accb6b4b492f67920c8c4bcc243c28c04ffc Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 15:31:06 -0700 Subject: [PATCH 02/16] Validate texture readback inputs before narrowing and allocation Reject invalid buffer offsets, lengths, face/layer indices, and uninitialized textures before readback work begins. Check full source and destination storage sizes before bgfx's uint32 conversion. Link bimg independently of image decoding so these checks also build with image loading disabled. Review-Group: E2 Source: PR #1892 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: ac3f4081ed11b26f509693c60f97b296790451e2 Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 29 ++++++++++++ Plugins/NativeEngine/CMakeLists.txt | 2 +- Plugins/NativeEngine/Source/NativeEngine.cpp | 45 +++++++++++++++---- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index fe267ba3e..4a98976ca 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -50,6 +50,35 @@ describe("Native texture readback", function () { // Native LoadRawTexture is also disabled when native image loading is off. const itWithRawTexture = hasGpuRendering && hasNativeImageLoading ? it : it.skip; + itWithRawTexture("rejects invalid native buffer and face numbers without modifying the destination", async function () { + const engine = new _native.Engine(); + const texture = engine.createTexture(); + try { + engine.initializeTexture(texture, 4, 4, false, _native.Engine.TEXTURE_FORMAT_RGBA8, false, false, 1); + for (const component of [7, 8, 9]) { + for (const invalid of [-2, 0.5, NaN, Infinity, 2 ** 32, 2 ** 32 + 1]) { + const destination = new Uint8Array(4).fill(91); + const request = [texture, 0, 0, 0, 1, 1, destination.buffer, 0, 4, -1]; + request[component] = invalid; + let error: unknown; + try { + await engine.readTexture(...request); + } catch (caught) { + error = caught; + } + if (!(error instanceof Error)) { + throw new Error(`Expected native readback component ${component}=${invalid} to reject`); + } + expect(error.message).to.contain(component === 9 ? "face/layer index" : "buffer offset and length"); + expect(Array.from(destination)).to.deep.equal([91, 91, 91, 91]); + } + } + } finally { + engine.deleteTexture(texture); + engine.dispose(); + } + }); + itWithRawTexture("returns bottom-origin RGBA8 crops and preserves destination offsets", async function () { const engine = new NativeEngine(); const scene = new Scene(engine); diff --git a/Plugins/NativeEngine/CMakeLists.txt b/Plugins/NativeEngine/CMakeLists.txt index 400bbb807..3d4604195 100644 --- a/Plugins/NativeEngine/CMakeLists.txt +++ b/Plugins/NativeEngine/CMakeLists.txt @@ -30,6 +30,7 @@ target_link_libraries(NativeEngine PUBLIC napi PRIVATE arcana PRIVATE bgfx + PRIVATE bimg PRIVATE bx PRIVATE GraphicsDevice PRIVATE GraphicsDeviceContext @@ -39,7 +40,6 @@ if(BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES) target_compile_definitions(NativeEngine PRIVATE BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES) target_link_libraries(NativeEngine - PRIVATE bimg PRIVATE bimg_encode PRIVATE bimg_decode) endif() diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index dc5d8439a..58306d30d 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -16,10 +16,10 @@ #include #include +#include #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES #include -#include #include #include @@ -2353,12 +2353,10 @@ namespace Babylon const double requestedWidth{info[4].As().DoubleValue()}; const double requestedHeight{info[5].As().DoubleValue()}; auto buffer{info[6].As()}; - uint32_t bufferOffset{info[7].As().Uint32Value()}; - uint32_t bufferLength{info[8].As().Uint32Value()}; + const double requestedBufferOffset{info[7].As().DoubleValue()}; + const double requestedBufferLength{info[8].As().DoubleValue()}; // Optional cube-map face index (0-5). -1 (or absent) means a plain 2D read. - const int32_t faceIndex{(info.Length() > 9 && info[9].IsNumber()) ? info[9].As().Int32Value() : -1}; - const bool isCubeFace{faceIndex >= 0}; - const uint16_t srcZ{isCubeFace ? static_cast(faceIndex) : static_cast(0)}; + const double requestedFaceIndex{(info.Length() > 9 && !info[9].IsUndefined()) ? info[9].As().DoubleValue() : -1}; const auto deferred{Napi::Promise::Deferred::New(env)}; @@ -2381,6 +2379,26 @@ namespace Babylon return deferred.Promise(); } } + if (!isUnsignedInteger(requestedBufferOffset, UINT32_MAX) || !isUnsignedInteger(requestedBufferLength, UINT32_MAX)) + { + deferred.Reject(Napi::Error::New(env, "readTexture buffer offset and length must be finite uint32 integers.").Value()); + return deferred.Promise(); + } + if (!(requestedFaceIndex >= -1 && requestedFaceIndex <= UINT16_MAX && std::floor(requestedFaceIndex) == requestedFaceIndex)) + { + deferred.Reject(Napi::Error::New(env, "readTexture face/layer index must be -1 or a finite uint16 integer.").Value()); + return deferred.Promise(); + } + if (!texture->IsValid()) + { + deferred.Reject(Napi::Error::New(env, "readTexture requires an initialized texture.").Value()); + return deferred.Promise(); + } + uint32_t bufferOffset{static_cast(requestedBufferOffset)}; + uint32_t bufferLength{static_cast(requestedBufferLength)}; + const int32_t faceIndex{static_cast(requestedFaceIndex)}; + const bool isCubeFace{faceIndex >= 0}; + const uint16_t srcZ{isCubeFace ? static_cast(faceIndex) : static_cast(0)}; uint8_t mipLevel{static_cast(requestedMipLevel)}; const uint16_t x{static_cast(requestedX)}; const uint16_t y{static_cast(requestedY)}; @@ -2388,7 +2406,6 @@ namespace Babylon const uint16_t height{static_cast(requestedHeight)}; const auto sourceTextureFormat{texture->Format()}; - // The face/layer index is JS-controlled and is forwarded to encoder->blit as srcZ, so validate it // before it can drive an out-of-bounds read inside bgfx. Babylon.js passes this argument for both // cube maps (face 0-5, six consecutive faces per array layer) and 2D arrays (slice index, which can @@ -2424,12 +2441,22 @@ namespace Babylon return deferred.Promise(); } + // Check the full storage size before bgfx narrows it to uint32. + const auto targetTextureFormat{bgfx::TextureFormat::RGBA8}; + const auto storageSize = [&](bgfx::TextureFormat::Enum format) { + return bimg::imageGetSize(nullptr, width, height, 1, false, false, 1, static_cast(format)); + }; + if (storageSize(sourceTextureFormat) > UINT32_MAX || storageSize(targetTextureFormat) > UINT32_MAX) + { + deferred.Reject(Napi::Error::New(env, "readTexture storage size exceeds uint32.").Value()); + return deferred.Promise(); + } + // Calculate storage sizes only after validating the requested mip and rectangle. bgfx::TextureInfo sourceTextureInfo{}; bgfx::calcTextureSize(sourceTextureInfo, width, height, /*depth*/ 1, /*cubeMap*/ false, /*hasMips*/ false, /*numLayers*/ 1, sourceTextureFormat); - // Always return pixel data in RGBA8 to match the web. - const auto targetTextureFormat{bgfx::TextureFormat::Enum::RGBA8}; + // Always return four-channel pixel data to match the web. bgfx::TextureInfo targetTextureInfo{}; bgfx::calcTextureSize(targetTextureInfo, width, height, /*depth*/ 1, /*cubeMap*/ false, /*hasMips*/ false, /*numLayers*/ 1, targetTextureFormat); From 01f3d26cb73ceb5c843e485d796987f0d3c72467 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 15:31:41 -0700 Subject: [PATCH 03/16] Brand native Canvas instances without JavaScript instanceof Track genuine native Canvas, Image, Gradient, and Path2D instances in an environment-local registry before unwrapping them. Reject prototype- spoofed inputs without depending on mutable JavaScript instanceof behavior, while preserving valid overloads. Add regressions for forged objects and legitimate Canvas operations. Review-Group: E7 Source: PR #1844 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 5333ecc57aebebb51753797a042de064d3391d6b Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 107 +++ .../Source/Tests.Canvas.Readback.cpp | 68 ++ Polyfills/Canvas/CMakeLists.txt | 1 + Polyfills/Canvas/Source/Canvas.cpp | 49 +- Polyfills/Canvas/Source/Canvas.h | 13 + Polyfills/Canvas/Source/Context.cpp | 67 +- Polyfills/Canvas/Source/Gradient.cpp | 16 +- Polyfills/Canvas/Source/Gradient.h | 9 +- Polyfills/Canvas/Source/Image.cpp | 9 + Polyfills/Canvas/Source/Image.h | 2 + .../Canvas/Source/NativeInstanceRegistry.h | 105 +++ Polyfills/Canvas/Source/Path2D.cpp | 760 +++++++++--------- Polyfills/Canvas/Source/Path2D.h | 8 +- 13 files changed, 818 insertions(+), 396 deletions(-) create mode 100644 Polyfills/Canvas/Source/NativeInstanceRegistry.h diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index 4a98976ca..93a03d711 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -605,6 +605,113 @@ describe("Canvas2D", function () { expect(ctx.strokeStyle).to.equal("#00ff00"); }); + it("rejects a prototype-spoofed object in place of a Path2D", function () { + // Prototype spoofing must not let fill() unwrap a foreign or nonexistent native object. + const ctx = createContext(); + const spoofedGradient: any = ctx.createLinearGradient(0, 0, 10, 10); + Object.setPrototypeOf(spoofedGradient, Path2D.prototype); + expect(spoofedGradient instanceof Path2D).to.equal(true); + + const bare: any = Object.create(Path2D.prototype); + expect(bare instanceof Path2D).to.equal(true); + + const realPath = new Path2D(); + for (const impostor of [spoofedGradient, bare]) { + expect(function () { ctx.fill(impostor); }).to.throw(); + expect(function () { ctx.stroke(impostor); }).to.throw(); + expect(function () { realPath.addPath(impostor); }).to.throw(); + // Non-Path2D arguments use the DOMString overload. + expect(function () { new Path2D(impostor); }).to.not.throw(); + } + }); + + it("ignores a prototype-spoofed object assigned to fillStyle or strokeStyle", function () { + // A forged gradient prototype must not authorize native unwrapping. + const ctx = createContext(); + const gradient = ctx.createLinearGradient(0, 0, 10, 10); + const spoofedPath: any = new Path2D(); + Object.setPrototypeOf(spoofedPath, Object.getPrototypeOf(gradient)); + + ctx.fillStyle = "#ff0000"; + ctx.strokeStyle = "#00ff00"; + ctx.fillStyle = spoofedPath; + ctx.strokeStyle = spoofedPath; + + // Per spec an unusable assignment leaves the previous value in place. + expect(ctx.fillStyle).to.equal("#ff0000"); + expect(ctx.strokeStyle).to.equal("#00ff00"); + + // The drawing path must stay usable rather than unwrapping the impostor. + expect(function () { ctx.fillRect(0, 0, 10, 10); }).to.not.throw(); + expect(function () { ctx.strokeRect(0, 0, 10, 10); }).to.not.throw(); + }); + + it("rejects a non-Path2D argument to fill and stroke", function () { + // Assert rejection, not the exception class: QuickJS reports native TypeErrors as InternalErrors. + const ctx = createContext(); + expect(function () { ctx.stroke("x"); }).to.throw(); + expect(function () { ctx.stroke({}); }).to.throw(); + expect(function () { ctx.stroke(5); }).to.throw(); + expect(function () { ctx.fill({}); }).to.throw(); + expect(function () { ctx.fill(5); }).to.throw(); + }); + + it("still accepts the valid fill and stroke argument forms", function () { + const ctx = createContext(); + const path = new Path2D("M0 0 L10 10"); + expect(function () { ctx.fill(); }).to.not.throw(); + expect(function () { ctx.stroke(); }).to.not.throw(); + // undefined selects the no-argument overload rather than being a bad Path2D. + expect(function () { ctx.fill(undefined); }).to.not.throw(); + expect(function () { ctx.stroke(undefined); }).to.not.throw(); + // fill() also takes a fill rule string. + expect(function () { ctx.fill("evenodd"); }).to.not.throw(); + expect(function () { ctx.fill(new String("evenodd") as any); }).to.not.throw(); + expect(function () { + ctx.fill({ toString: function () { return "nonzero"; } } as any); + }).to.not.throw(); + expect(function () { ctx.fill(path); }).to.not.throw(); + expect(function () { ctx.fill(path, "nonzero"); }).to.not.throw(); + expect(function () { ctx.fill(path, new String("evenodd") as any); }).to.not.throw(); + expect(function () { ctx.stroke(path); }).to.not.throw(); + expect(function () { ctx.fill("invalid"); }).to.throw(); + expect(function () { ctx.fill(path, "invalid"); }).to.throw(); + }); + + it("rejects a non-Path2D argument to Path2D.addPath", function () { + // Missing and unrelated arguments must not reach native unwrapping. + const path = new Path2D(); + expect(function () { path.addPath(); }).to.throw(); + expect(function () { path.addPath("x"); }).to.throw(); + expect(function () { path.addPath({}); }).to.throw(); + expect(function () { path.addPath(new Path2D("M0 0 L5 5")); }).to.not.throw(); + }); + + it("rejects an invalid native source argument to drawImage", function () { + // Native-looking impostors must throw, not AV via an unchecked Unwrap. + const ctx = createContext(); + const realCanvas = new _native.Canvas(); + const spoofedCanvas = Object.create(Object.getPrototypeOf(realCanvas)); + expect(spoofedCanvas instanceof _native.Canvas).to.equal(true); + expect(function () { ctx.drawImage({}, 0, 0); }).to.throw(); + expect(function () { ctx.drawImage(new Path2D(), 0, 0); }).to.throw(); + expect(function () { ctx.drawImage(spoofedCanvas, 0, 0); }).to.throw(); + realCanvas.dispose(); + }); + + it("treats a non-Path2D Path2D() argument as path data", function () { + // Non-Path2D objects use the DOMString constructor overload. + expect(function () { new Path2D({}); }).to.not.throw(); + expect(function () { new Path2D(5); }).to.not.throw(); + expect(function () { new Path2D(); }).to.not.throw(); + const source = new Path2D("M0 0 L10 10"); + const ctx = createContext(); + expect(function () { ctx.fill(new Path2D(source)); }).to.not.throw(); + expect(function () { + ctx.fill(new Path2D({ toString: function () { return "M0 0 L10 10"; } })); + }).to.not.throw(); + }); + it("accepts a CanvasGradient as fillStyle", function () { const ctx = createContext(); const gradient = ctx.createLinearGradient(0, 0, 64, 64); diff --git a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp index ecfc2fb38..9756317dd 100644 --- a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp +++ b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp @@ -1,10 +1,15 @@ #include +#include #include #include #include #include "../../../Polyfills/Canvas/Source/Canvas.h" #include "../../../Polyfills/Canvas/Source/Context.h" +#include "../../../Polyfills/Canvas/Source/Gradient.h" +#include "../../../Polyfills/Canvas/Source/Image.h" +#include "../../../Polyfills/Canvas/Source/NativeInstanceRegistry.h" +#include "../../../Polyfills/Canvas/Source/Path2D.h" #include "../../../Polyfills/Canvas/Source/nanovg/nanovg.h" #include @@ -55,6 +60,69 @@ namespace } } +TEST(CanvasReadback, HostOwnerMayOutliveRuntime) +{ + Babylon::Graphics::Device device{g_deviceConfig}; + std::optional canvas; + { + Babylon::AppRuntime runtime{}; + std::promise initialized; + runtime.Dispatch([&](Napi::Env env) { + device.AddToJavaScript(env); + canvas.emplace(Babylon::Polyfills::Canvas::Initialize(env)); + initialized.set_value(); + }); + initialized.get_future().get(); + } + canvas.reset(); +} + +TEST(CanvasReadback, NativeBrandsRequireTheOriginalReceiver) +{ + RunCanvasTest([](Napi::Env env) { + using namespace Babylon::Polyfills::Internal; + const auto native = Babylon::JsRuntime::NativeObject::GetFromJavaScript(env); + const auto canvas = native.Get("Canvas").As().New({}); + const auto image = native.Get("Image").As().New({}); + const auto path = native.Get("Path2D").As().New({}); + const auto context = canvas.Get("getContext").As() + .Call(canvas, {Napi::String::New(env, "2d")}).As(); + const auto gradient = context.Get("createLinearGradient").As() + .Call(context, {Napi::Number::New(env, 0), Napi::Number::New(env, 0), + Napi::Number::New(env, 8), Napi::Number::New(env, 8)}).As(); + const auto check = [&](const Napi::Object& object, auto unwrap) { + ASSERT_NE(unwrap(env, object), nullptr); + auto copied = Napi::Object::New(env); + copied.Set("__nativeInstance", object.Get("__nativeInstance")); + EXPECT_EQ(unwrap(env, copied), nullptr); + const auto objectConstructor = env.Global().Get("Object").As(); + const auto inherited = objectConstructor.Get("create").As().Call(objectConstructor, {object}); + EXPECT_EQ(unwrap(env, inherited), nullptr); + EXPECT_NE(unwrap(env, object), nullptr); + }; + check(canvas, NativeCanvas::TryUnwrap); + check(image, NativeCanvasImage::TryUnwrap); + check(path, NativeCanvasPath2D::TryUnwrap); + check(gradient, CanvasGradient::TryUnwrap); + EXPECT_EQ(NativeCanvas::TryUnwrap(env, image), nullptr); + EXPECT_EQ(NativeCanvasImage::TryUnwrap(env, canvas), nullptr); + + // Re-register one native address with a different wrapper, simulating address reuse. + int token{}; + using Registry = NativeInstanceRegistry; + const auto remove = gsl::finally([&] { Registry::Remove(&token); }); + const auto registerToken = Napi::Function::New(env, [&](const Napi::CallbackInfo& info) { Registry::Add(info, &token); }); + const auto oldWrapper = Napi::Object::New(env); + const auto newWrapper = Napi::Object::New(env); + registerToken.Call(oldWrapper, {}); + EXPECT_EQ(Registry::TryUnwrap(env, oldWrapper), &token); + Registry::Remove(&token); + registerToken.Call(newWrapper, {}); + EXPECT_EQ(Registry::TryUnwrap(env, oldWrapper), nullptr); + EXPECT_EQ(Registry::TryUnwrap(env, newWrapper), &token); + }); +} + TEST(CanvasReadback, DrawImageRejectsArityAndNoOpGeometryBeforeReadbackOrUpload) { RunCanvasTest([](Napi::Env env) { diff --git a/Polyfills/Canvas/CMakeLists.txt b/Polyfills/Canvas/CMakeLists.txt index 3103b004c..22affaf88 100644 --- a/Polyfills/Canvas/CMakeLists.txt +++ b/Polyfills/Canvas/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES "Source/Context.h" "Source/MeasureText.cpp" "Source/MeasureText.h" + "Source/NativeInstanceRegistry.h" "Source/Gradient.cpp" "Source/Gradient.h" "Source/Font.cpp" diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index e08b443d1..5bff7dc91 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -11,6 +11,7 @@ #include "Colors.h" #include "Gradient.h" #include "Font.h" +#include "NativeInstanceRegistry.h" #include #include #include @@ -47,15 +48,23 @@ namespace Babylon::Polyfills::Internal JsRuntime::NativeObject::GetFromJavaScript(env).Set(JS_CONSTRUCTOR_NAME, func); } + NativeCanvas* NativeCanvas::TryUnwrap(Napi::Env env, const Napi::Value& value) + { + return NativeInstanceRegistry::TryUnwrap(env, value); + } + NativeCanvas::NativeCanvas(const Napi::CallbackInfo& info) : Napi::ObjectWrap{info} , m_graphicsContext{Graphics::DeviceContext::GetFromJavaScript(info.Env())} , Polyfills::Canvas::Impl::MonitoredResource{Polyfills::Canvas::Impl::GetFromJavaScript(info.Env())} { + NativeInstanceRegistry::Add(info, this); } NativeCanvas::~NativeCanvas() { + NativeInstanceRegistry::Remove(this); + // Canvas and Context form a JS cycle; finalizer order is not guaranteed. // Clear the reverse pointer first so Context::~Context cannot touch us. if (m_context != nullptr) @@ -307,26 +316,58 @@ namespace Babylon::Polyfills::Internal namespace Babylon::Polyfills { + struct Canvas::Impl::JavaScriptData + { + Canvas::Impl& Owner; + Napi::FunctionReference WeakSetConstructor; + Napi::FunctionReference WeakSetAdd; + Napi::FunctionReference WeakSetHas; + }; + Canvas::Impl::Impl(Napi::Env env) : m_env{env} { AddToJavaScript(env); } + Canvas::Impl::WeakIdentity Canvas::Impl::CreateWeakIdentity(const Napi::Object& value) + { + const auto& data = GetJavaScriptData(m_env); + const auto receivers = data.WeakSetConstructor.New({}); + data.WeakSetAdd.Call(receivers, {value}); + return {Napi::Persistent(receivers), Napi::Persistent(data.WeakSetHas.Value())}; + } + void Canvas::Impl::AddToJavaScript(Napi::Env env) { - JsRuntime::NativeObject::GetFromJavaScript(env) - .Set(JS_CANVAS_NAME, Napi::External::New(env, this)); + const auto constructor = env.Global().Get("WeakSet").As(); + const auto prototype = constructor.Get("prototype").As(); + // The host Canvas may outlive its environment; finalize N-API references with JavaScript. + auto data = std::make_unique(JavaScriptData{ + *this, + Napi::Persistent(constructor), + Napi::Persistent(prototype.Get("add").As()), + Napi::Persistent(prototype.Get("has").As())}); + const auto external = Napi::External::New(env, data.get(), [](Napi::Env, JavaScriptData* data) { + delete data; + }); + data.release(); + JsRuntime::NativeObject::GetFromJavaScript(env).Set(JS_CANVAS_NAME, external); } - Canvas::Impl& Canvas::Impl::GetFromJavaScript(Napi::Env env) + Canvas::Impl::JavaScriptData& Canvas::Impl::GetJavaScriptData(Napi::Env env) { return *JsRuntime::NativeObject::GetFromJavaScript(env) .Get(JS_CANVAS_NAME) - .As>() + .As>() .Data(); } + Canvas::Impl& Canvas::Impl::GetFromJavaScript(Napi::Env env) + { + return GetJavaScriptData(env).Owner; + } + void Canvas::Impl::AddMonitoredResource(MonitoredResource* monitoredResource) { if (std::find(m_monitoredResources.begin(), m_monitoredResources.end(), monitoredResource) == m_monitoredResources.end()) diff --git a/Polyfills/Canvas/Source/Canvas.h b/Polyfills/Canvas/Source/Canvas.h index 6cc504a90..abe2cc6b1 100644 --- a/Polyfills/Canvas/Source/Canvas.h +++ b/Polyfills/Canvas/Source/Canvas.h @@ -17,6 +17,14 @@ namespace Babylon::Polyfills void FlushGraphicResources(); + struct WeakIdentity + { + Napi::ObjectReference Receivers; + Napi::FunctionReference Has; + }; + + WeakIdentity CreateWeakIdentity(const Napi::Object& value); + static Canvas::Impl& GetFromJavaScript(Napi::Env env); struct MonitoredResource @@ -39,6 +47,9 @@ namespace Babylon::Polyfills }; private: + struct JavaScriptData; + static JavaScriptData& GetJavaScriptData(Napi::Env env); + Napi::Env m_env; void AddToJavaScript(Napi::Env env); @@ -61,6 +72,8 @@ namespace Babylon::Polyfills::Internal public: static void Initialize(Napi::Env env); + static NativeCanvas* TryUnwrap(Napi::Env env, const Napi::Value& value); + explicit NativeCanvas(const Napi::CallbackInfo& info); virtual ~NativeCanvas(); diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index b26d762f5..11b1b0159 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -62,6 +62,15 @@ namespace Babylon::Polyfills::Internal value <= static_cast(std::numeric_limits::max()) && value == std::trunc(value); } + + void ValidateFillRule(Napi::Env env, const Napi::Value& value) + { + const auto fillRule = value.ToString().Utf8Value(); + if (fillRule != "nonzero" && fillRule != "evenodd") + { + throw Napi::TypeError::New(env, "Context2D.fill: the fill rule must be \"nonzero\" or \"evenodd\"."); + } + } } void Context::Initialize(Napi::Env env) @@ -201,12 +210,16 @@ namespace Babylon::Polyfills::Internal } else if (std::holds_alternative(m_state.fillStyle)) { - CanvasGradient* gradient = CanvasGradient::Unwrap(std::get(m_state.fillStyle)->Value()); + CanvasGradient* gradient = CanvasGradient::TryUnwrap(info.Env(), std::get(m_state.fillStyle)->Value()); + if (gradient == nullptr) + { + throw Napi::TypeError::New(info.Env(), "fillStyle must be a string or CanvasGradient."); + } nvgFillPaint(*m_nvg, gradient->Paint()); } else { - throw Napi::Error::New(info.Env(), "Fillstyle is not a color string or a gradient."); + throw Napi::TypeError::New(info.Env(), "fillStyle must be a string or CanvasGradient."); } } @@ -223,12 +236,16 @@ namespace Babylon::Polyfills::Internal } else if (std::holds_alternative(m_state.strokeStyle)) { - CanvasGradient* gradient = CanvasGradient::Unwrap(std::get(m_state.strokeStyle)->Value()); + CanvasGradient* gradient = CanvasGradient::TryUnwrap(info.Env(), std::get(m_state.strokeStyle)->Value()); + if (gradient == nullptr) + { + throw Napi::TypeError::New(info.Env(), "strokeStyle must be a string or CanvasGradient."); + } nvgStrokePaint(*m_nvg, gradient->Paint()); } else { - throw Napi::Error::New(info.Env(), "Strokestyle is not a color string or a gradient."); + throw Napi::TypeError::New(info.Env(), "strokeStyle must be a string or CanvasGradient."); } } @@ -345,10 +362,21 @@ namespace Babylon::Polyfills::Internal { SetFilterStack(); - const NativeCanvasPath2D* path = info.Length() >= 1 && info[0].IsObject() - ? NativeCanvasPath2D::Unwrap(info[0].As()) - : nullptr; // TODO: handle fillRule: nonzero, evenodd + // fill(path?, fillRule?) — distinguish Path2D from the string-converted enum overload. + const NativeCanvasPath2D* path = nullptr; + if (info.Length() >= 1 && !info[0].IsUndefined()) + { + path = NativeCanvasPath2D::TryUnwrap(info.Env(), info[0]); + if (path == nullptr) + { + ValidateFillRule(info.Env(), info[0]); + } + } + if (path != nullptr && info.Length() >= 2 && !info[1].IsUndefined()) + { + ValidateFillRule(info.Env(), info[1]); + } // draw Path2D if exists if (path != nullptr) @@ -654,8 +682,18 @@ namespace Babylon::Polyfills::Internal void Context::Stroke(const Napi::CallbackInfo& info) { + // stroke(path?) — reject non-Path2D first arg. + const NativeCanvasPath2D* path = nullptr; + if (info.Length() >= 1 && !info[0].IsUndefined()) + { + path = NativeCanvasPath2D::TryUnwrap(info.Env(), info[0]); + if (path == nullptr) + { + throw Napi::TypeError::New(info.Env(), "Context2D.stroke: the first argument is not a Path2D."); + } + } + // draw Path2D if exists - const NativeCanvasPath2D* path = info.Length() == 1 ? NativeCanvasPath2D::Unwrap(info[0].As()) : nullptr; if (path != nullptr) { PlayPath2D(path); @@ -1255,11 +1293,15 @@ namespace Babylon::Polyfills::Internal Napi::Object imageObj = info[0].As(); // Retain the source kind before coercion can change its prototype or properties. // Canvas takes precedence over the structural ImageBitmap shape. - const auto canvasCtorVal = JsRuntime::NativeObject::GetFromJavaScript(info.Env()).Get("Canvas"); - NativeCanvas* const srcCanvas = canvasCtorVal.IsFunction() && imageObj.InstanceOf(canvasCtorVal.As()) - ? NativeCanvas::Unwrap(imageObj) + NativeCanvas* const srcCanvas = NativeCanvas::TryUnwrap(info.Env(), imageObj); + const NativeCanvasImage* const canvasImage = srcCanvas == nullptr + ? NativeCanvasImage::TryUnwrap(info.Env(), imageObj) : nullptr; - const bool isImageBitmap = srcCanvas == nullptr && imageObj.Has("data") && imageObj.Get("data").IsTypedArray(); + const bool isImageBitmap = srcCanvas == nullptr && canvasImage == nullptr && imageObj.Has("data") && imageObj.Get("data").IsTypedArray(); + if (srcCanvas == nullptr && canvasImage == nullptr && !isImageBitmap) + { + throw Napi::TypeError::New(info.Env(), "drawImage: first argument must be a Canvas, Image, or ImageBitmap-like object."); + } // Coercion can also resize the source. Do it once, before reading dimensions // or creating any graphics resources. @@ -1386,7 +1428,6 @@ namespace Babylon::Polyfills::Internal #endif } - const NativeCanvasImage* canvasImage = NativeCanvasImage::Unwrap(imageObj); const auto rectangles = ParseDrawImageRectangles(arguments, canvasImage->GetWidth(), canvasImage->GetHeight()); if (!rectangles) { diff --git a/Polyfills/Canvas/Source/Gradient.cpp b/Polyfills/Canvas/Source/Gradient.cpp index e237157a0..1f7d53356 100644 --- a/Polyfills/Canvas/Source/Gradient.cpp +++ b/Polyfills/Canvas/Source/Gradient.cpp @@ -3,6 +3,7 @@ #include "Context.h" #include "Gradient.h" #include "Colors.h" +#include "NativeInstanceRegistry.h" #include #include @@ -120,13 +121,12 @@ namespace Babylon::Polyfills::Internal bool CanvasGradient::IsInstance(Napi::Env env, const Napi::Value& value) { - if (!value.IsObject()) - { - return false; - } + return TryUnwrap(env, value) != nullptr; + } - const auto constructor = JsRuntime::NativeObject::GetFromJavaScript(env).Get(JS_CANVAS_GRADIENT_CONSTRUCTOR_NAME); - return constructor.IsFunction() && value.As().InstanceOf(constructor.As()); + CanvasGradient* CanvasGradient::TryUnwrap(Napi::Env env, const Napi::Value& value) + { + return NativeInstanceRegistry::TryUnwrap(env, value); } Napi::Object CanvasGradient::CreateLinear(Napi::Env env, const std::shared_ptr& context, float x0, float y0, float x1, float y1) @@ -163,10 +163,14 @@ namespace Babylon::Polyfills::Internal r0 = info[4].As().FloatValue(); r1 = info[5].As().FloatValue(); } + + // Register after successful construction only. + NativeInstanceRegistry::Add(info, this); } CanvasGradient::~CanvasGradient() { + NativeInstanceRegistry::Remove(this); Dispose(); } diff --git a/Polyfills/Canvas/Source/Gradient.h b/Polyfills/Canvas/Source/Gradient.h index 913a257b5..c69cd19af 100644 --- a/Polyfills/Canvas/Source/Gradient.h +++ b/Polyfills/Canvas/Source/Gradient.h @@ -16,12 +16,13 @@ namespace Babylon::Polyfills::Internal static Napi::Object CreateLinear(Napi::Env env, const std::shared_ptr& context, float x0, float y0, float x1, float y1); static Napi::Object CreateRadial(Napi::Env env, const std::shared_ptr& context, float x0, float y0, float r0, float x1, float y1, float r1); - // True only for objects this polyfill's own constructor produced. Unwrap() is a - // napi_unwrap, which dereferences whatever the object's slot happens to hold, so - // callers must establish the type first: `ctx.strokeStyle = {}` is reachable from - // script and would otherwise unwrap an object that was never wrapped. + // True only for objects this polyfill's own constructor produced. Never use + // ObjectWrap::Unwrap on an unchecked object; see NativeInstanceRegistry. static bool IsInstance(Napi::Env env, const Napi::Value& value); + // Returns the wrapped gradient, or nullptr when `value` is not one. + static CanvasGradient* TryUnwrap(Napi::Env env, const Napi::Value& value); + explicit CanvasGradient(const Napi::CallbackInfo& info); virtual ~CanvasGradient(); diff --git a/Polyfills/Canvas/Source/Image.cpp b/Polyfills/Canvas/Source/Image.cpp index 306a5b781..af8489bac 100644 --- a/Polyfills/Canvas/Source/Image.cpp +++ b/Polyfills/Canvas/Source/Image.cpp @@ -3,6 +3,7 @@ #include "Canvas.h" #include "Image.h" #include "Context.h" +#include "NativeInstanceRegistry.h" #include #include #include @@ -43,15 +44,23 @@ namespace Babylon::Polyfills::Internal JsRuntime::NativeObject::GetFromJavaScript(env).Set(JS_IMAGE_CONSTRUCTOR_NAME, func); } + NativeCanvasImage* NativeCanvasImage::TryUnwrap(Napi::Env env, const Napi::Value& value) + { + return NativeInstanceRegistry::TryUnwrap(env, value); + } + NativeCanvasImage::NativeCanvasImage(const Napi::CallbackInfo& info) : Napi::ObjectWrap{info} , m_runtimeScheduler{JsRuntime::GetFromJavaScript(info.Env())} , m_cancellationSource{std::make_shared()} { + // Register after successful construction only. + NativeInstanceRegistry::Add(info, this); } NativeCanvasImage::~NativeCanvasImage() { + NativeInstanceRegistry::Remove(this); Dispose(); } diff --git a/Polyfills/Canvas/Source/Image.h b/Polyfills/Canvas/Source/Image.h index bc40b6025..5c3b28a60 100644 --- a/Polyfills/Canvas/Source/Image.h +++ b/Polyfills/Canvas/Source/Image.h @@ -19,6 +19,8 @@ namespace Babylon::Polyfills::Internal public: static void Initialize(Napi::Env env); + static NativeCanvasImage* TryUnwrap(Napi::Env env, const Napi::Value& value); + explicit NativeCanvasImage(const Napi::CallbackInfo& info); virtual ~NativeCanvasImage(); diff --git a/Polyfills/Canvas/Source/NativeInstanceRegistry.h b/Polyfills/Canvas/Source/NativeInstanceRegistry.h new file mode 100644 index 000000000..a611b1ac3 --- /dev/null +++ b/Polyfills/Canvas/Source/NativeInstanceRegistry.h @@ -0,0 +1,105 @@ +#pragma once + +#include "Canvas.h" +#include +#include +#include +#include + +namespace Babylon::Polyfills::Internal +{ + // Pointer brands are lookup hints; private weak identities authorize unwrapping. + template + class NativeInstanceRegistry + { + public: + // Register at end of ctor only — a throwing ctor never reaches the dtor. + static void Add(const Napi::CallbackInfo& info, T* instance) + { + // Use Object.defineProperty for immutable, non-enumerable brands on all JsRuntimeHost ports. + Napi::Object self = info.This().As(); + Napi::Object descriptor = Napi::Object::New(info.Env()); + descriptor.Set("value", Napi::External::New(info.Env(), instance)); + Napi::Object object = info.Env().Global().Get("Object").As(); + object.Get("defineProperty").As().Call( + object, + {self, Napi::String::New(info.Env(), BRAND_NAME), descriptor}); + + Identity identity{self}; + const std::scoped_lock lock{Mutex()}; + Instances().try_emplace(instance, std::move(identity)); + } + + static void Remove(const T* instance) + { + const std::scoped_lock lock{Mutex()}; + Instances().erase(instance); + } + + static T* TryUnwrap(Napi::Env env, const Napi::Value& value) + { + if (!value.IsObject()) + { + return nullptr; + } + + Napi::Value brand{env.Undefined()}; + try + { + brand = value.As().Get(BRAND_NAME); + } + catch (...) + { + } + + if (env.IsExceptionPending()) + { + (void)env.GetAndClearPendingException(); + } + + if (!brand.IsExternal()) + { + return nullptr; + } + + T* const candidate = brand.As>().Data(); + + const std::scoped_lock lock{Mutex()}; + const auto entry = Instances().find(candidate); + return entry != Instances().end() && entry->second.Matches(env, value) ? candidate : nullptr; + } + + private: + static constexpr const char* BRAND_NAME{"__nativeInstance"}; + + struct Identity + { + napi_env Env; + Canvas::Impl::WeakIdentity Receiver; + + explicit Identity(const Napi::Object& self) + : Env{self.Env()} + , Receiver{Canvas::Impl::GetFromJavaScript(self.Env()).CreateWeakIdentity(self)} + { + } + + bool Matches(Napi::Env env, const Napi::Value& value) const + { + return env == Env && Receiver.Has.Call(Receiver.Receivers.Value(), {value}).template As().Value(); + } + }; + + // Keep storage available to late finalizers during static teardown. + static std::mutex& Mutex() + { + static auto* mutex{new std::mutex{}}; + return *mutex; + } + + static std::unordered_map& Instances() + { + static auto* instances{new std::unordered_map{}}; + return *instances; + } + }; +} \ No newline at end of file diff --git a/Polyfills/Canvas/Source/Path2D.cpp b/Polyfills/Canvas/Source/Path2D.cpp index 2060e76eb..fa3b37058 100644 --- a/Polyfills/Canvas/Source/Path2D.cpp +++ b/Polyfills/Canvas/Source/Path2D.cpp @@ -1,368 +1,392 @@ -#include -#include -#include -#include "Canvas.h" -#include "Path2D.h" -#include - -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpedantic" -#endif - -#define NANOSVG_IMPLEMENTATION // Expands implementation -#include "nanosvg.h" - -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif - -namespace Babylon::Polyfills::Internal -{ - static constexpr auto JS_PATH2D_CONSTRUCTOR_NAME = "Path2D"; - - void NativeCanvasPath2D::Initialize(Napi::Env env) - { - Napi::HandleScope scope{env}; - - Napi::Function func = DefineClass( - env, - JS_PATH2D_CONSTRUCTOR_NAME, - { - InstanceMethod("addPath", &NativeCanvasPath2D::AddPath), - InstanceMethod("closePath", &NativeCanvasPath2D::ClosePath), - InstanceMethod("moveTo", &NativeCanvasPath2D::MoveTo), - InstanceMethod("lineTo", &NativeCanvasPath2D::LineTo), - InstanceMethod("bezierCurveTo", &NativeCanvasPath2D::BezierCurveTo), - InstanceMethod("quadraticCurveTo", &NativeCanvasPath2D::QuadraticCurveTo), - InstanceMethod("arc", &NativeCanvasPath2D::Arc), - InstanceMethod("arcTo", &NativeCanvasPath2D::ArcTo), - InstanceMethod("ellipse", &NativeCanvasPath2D::Ellipse), - InstanceMethod("rect", &NativeCanvasPath2D::Rect), - InstanceMethod("roundRect", &NativeCanvasPath2D::RoundRect), - }); - - JsRuntime::NativeObject::GetFromJavaScript(env).Set(JS_PATH2D_CONSTRUCTOR_NAME, func); - - // Browsers expose Path2D as a global constructor, and Babylon.js relies on that: the generic - // engines call `new Path2D(d)` from AbstractEngine.createCanvasPath2D. Only NativeEngine - // overrides that method to use `_native.Path2D`, so without the global any portable browser - // code doing `new Path2D(...)` fails with "Path2D is not defined". - auto global = env.Global(); - - // Claim the global unconditionally. A Path2D from anywhere else cannot be drawn by this - // Context -- Context::Fill unwraps whatever object it is handed as a NativeCanvasPath2D -- - // so deferring to a foreign constructor would only guarantee that `ctx.fill(new Path2D(d))` - // unwraps an object that was never wrapped. Canvas::Initialize calls this while the - // polyfill is still installing, so anything already there came from outside it. - global.Set(JS_PATH2D_CONSTRUCTOR_NAME, func); - } - - NativeCanvasPath2D::NativeCanvasPath2D(const Napi::CallbackInfo& info) - : Napi::ObjectWrap{info} - , m_commands{std::deque()} - { - const NativeCanvasPath2D* path = info.Length() == 1 && info[0].IsObject() - ? NativeCanvasPath2D::Unwrap(info[0].As()) - : nullptr; - const std::string d = info.Length() == 1 && info[0].IsString() ? info[0].As().Utf8Value() : ""; - - if (path != nullptr) - { - for (const auto& command : *path) - { - m_commands.push_back(command); - } - } - - if (!d.empty()) - { - NSVGparser* parser = nsvg__createParser(); - const char* path[] = {"d", d.c_str(), NULL}; - const char** attr = {path}; - - assert(strcmp(attr[0], "d") == 0); - assert(!attr[2]); // nsvg__parsePath terminates attr parsing on falsy - - nsvg__parsePath(parser, attr); - - for (NSVGshape *shape = parser->image->shapes; shape != NULL; shape = shape->next) { - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - for (int i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - - auto x0 = p[0]; // start x, same as end x of previous - auto y0 = p[1]; // start y, same as end y of previous - auto cpx1 = p[2]; - auto cpy1 = p[3]; - auto cpx2 = p[4]; - auto cpy2 = p[5]; - auto x1 = p[6]; // end x - auto y1 = p[7]; // end y - - // Only need to move on new shape - if (i == 0) - { - Path2DCommandArgs moveArgs = {}; - moveArgs.moveTo = {x0, y0}; - AppendCommand(P2D_MOVETO, moveArgs); - } - - Path2DCommandArgs args = {}; - args.bezierTo = {cpx1, cpy1, cpx2, cpy2, x1, y1}; - AppendCommand(P2D_BEZIERTO, args); - } - } - } - - nsvg__deleteParser(parser); - } - } - - typename std::deque::iterator NativeCanvasPath2D::begin() - { - return m_commands.begin(); - } - - typename std::deque::iterator NativeCanvasPath2D::end() - { - return m_commands.end(); - } - - typename std::deque::const_iterator NativeCanvasPath2D::begin() const - { - return m_commands.begin(); - } - - typename std::deque::const_iterator NativeCanvasPath2D::end() const - { - return m_commands.end(); - } - - void NativeCanvasPath2D::AppendCommand(Path2DCommandTypes type, Path2DCommandArgs args) - { - m_commands.push_back({type, args}); - } - - void NativeCanvasPath2D::AddPath(const Napi::CallbackInfo& info) - { - const NativeCanvasPath2D* path = NativeCanvasPath2D::Unwrap(info[0].As()); - - // optional transform arg - bool xformInvReady{false}; - float xformInv[6]; - if (info.Length() == 2) - { - Napi::Object transform = info[1].As(); - auto a = transform.Get("a").As().FloatValue(); - auto b = transform.Get("b").As().FloatValue(); - auto c = transform.Get("c").As().FloatValue(); - auto d = transform.Get("d").As().FloatValue(); - auto e = transform.Get("e").As().FloatValue(); - auto f = transform.Get("f").As().FloatValue(); - - float xform[6] = {a, b, c, d, e, f}; - nsvg__xformInverse(xformInv, xform); - xformInvReady = true; - - Path2DCommandArgs args = {}; - args.transform = { xform[0], xform[1], xform[2], xform[3], xform[4], xform[5] }; - AppendCommand(P2D_TRANSFORM, args); - } - - for (const auto& command : *path) - { - m_commands.push_back(command); - } - - // invert transform after all commands played - if (info.Length() == 2) - { - assert(xformInvReady); - Path2DCommandArgs argsInv = {}; - argsInv.transform = {xformInv[0], xformInv[1], xformInv[2], xformInv[3], xformInv[4], xformInv[5]}; - AppendCommand(P2D_TRANSFORM, argsInv); - } - } - - void NativeCanvasPath2D::ClosePath(const Napi::CallbackInfo& info) - { - AppendCommand(P2D_CLOSE, {}); - } - - void NativeCanvasPath2D::MoveTo(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.moveTo = {x, y}; - AppendCommand(P2D_MOVETO, args); - } - - void NativeCanvasPath2D::LineTo(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.lineTo = {x, y}; - AppendCommand(P2D_LINETO, args); - } - - void NativeCanvasPath2D::BezierCurveTo(const Napi::CallbackInfo& info) - { - const auto cp1x = static_cast(info[0].As().DoubleValue()); - const auto cp1y = static_cast(info[1].As().DoubleValue()); - const auto cp2x = static_cast(info[2].As().DoubleValue()); - const auto cp2y = static_cast(info[3].As().DoubleValue()); - const auto x = static_cast(info[4].As().DoubleValue()); - const auto y = static_cast(info[5].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.bezierTo = {cp1x, cp1y, cp2x, cp2y, x, y}; - AppendCommand(P2D_BEZIERTO, args); - } - - void NativeCanvasPath2D::QuadraticCurveTo(const Napi::CallbackInfo& info) - { - const auto cpx = static_cast(info[0].As().DoubleValue()); - const auto cpy = static_cast(info[1].As().DoubleValue()); - const auto x = static_cast(info[2].As().DoubleValue()); - const auto y = static_cast(info[3].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.quadTo = {cpx, cpy, x, y}; - AppendCommand(P2D_QUADTO, args); - } - - void NativeCanvasPath2D::Arc(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - const auto radius = static_cast(info[2].As().DoubleValue()); - const auto startAngle = static_cast(info[3].As().DoubleValue()); - const auto endAngle = static_cast(info[4].As().DoubleValue()); - const auto counterclockwise = info.Length() == 6 ? info[5].As() : false; - - Path2DCommandArgs args = {}; - args.arc = {x, y, radius, startAngle, endAngle, counterclockwise}; - AppendCommand(P2D_ARC, args); - } - - void NativeCanvasPath2D::ArcTo(const Napi::CallbackInfo& info) - { - const auto x1 = static_cast(info[0].As().DoubleValue()); - const auto y1 = static_cast(info[1].As().DoubleValue()); - const auto x2 = static_cast(info[2].As().DoubleValue()); - const auto y2 = static_cast(info[3].As().DoubleValue()); - const auto radius = static_cast(info[4].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.arcTo = {x1, y1, x2, y2, radius}; - AppendCommand(P2D_ARCTO, args); - } - - void NativeCanvasPath2D::Ellipse(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - const auto radiusX = static_cast(info[2].As().DoubleValue()); - const auto radiusY = static_cast(info[3].As().DoubleValue()); - const auto rotation = static_cast(info[4].As().DoubleValue()); - const auto startAngle = static_cast(info[5].As().DoubleValue()); - const auto endAngle = static_cast(info[6].As().DoubleValue()); - const auto counterclockwise = info.Length() == 8 ? info[7].As() : false; - - Path2DCommandArgs args = {}; - args.ellipse = {x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise}; - AppendCommand(P2D_ELLIPSE, args); - } - - void NativeCanvasPath2D::Rect(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - const auto width = static_cast(info[2].As().DoubleValue()); - const auto height = static_cast(info[3].As().DoubleValue()); - - Path2DCommandArgs args = {}; - args.rect = {x, y, width, height}; - AppendCommand(P2D_RECT, args); - } - - void NativeCanvasPath2D::RoundRect(const Napi::CallbackInfo& info) - { - const auto x = static_cast(info[0].As().DoubleValue()); - const auto y = static_cast(info[1].As().DoubleValue()); - const auto width = static_cast(info[2].As().DoubleValue()); - const auto height = static_cast(info[3].As().DoubleValue()); - const auto radii = info[4]; - - if (radii.IsNumber()) - { - const auto radius = radii.As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRect = {x, y, width, height, radius}; - AppendCommand(P2D_ROUNDRECT, args); - } - else if (radii.IsArray()) - { - const auto radiiArray = radii.As(); - const auto radiiArrayLength = radiiArray.Length(); - if (radiiArrayLength == 1) - { - const auto radius = radiiArray[0u].As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRect = {x, y, width, height, radius}; - AppendCommand(P2D_ROUNDRECT, args); - } - else if (radiiArrayLength == 2) - { - const auto topLeftBottomRight = radiiArray[0u].As().FloatValue(); - const auto topRightBottomLeft = radiiArray[1u].As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRectVarying = {x, y, width, height, topLeftBottomRight, topRightBottomLeft, topLeftBottomRight, topRightBottomLeft}; - AppendCommand(P2D_ROUNDRECTVARYING, args); - } - else if (radiiArrayLength == 3) - { - const auto topLeft = radiiArray[0u].As().FloatValue(); - const auto topRightBottomLeft = radiiArray[1u].As().FloatValue(); - const auto bottomRight = radiiArray[2u].As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRectVarying = {x, y, width, height, topLeft, topRightBottomLeft, bottomRight, topRightBottomLeft}; - AppendCommand(P2D_ROUNDRECTVARYING, args); - } - else if (radiiArrayLength == 4) - { - const auto topLeft = radiiArray[0u].As().FloatValue(); - const auto topRight = radiiArray[1u].As().FloatValue(); - const auto bottomRight = radiiArray[2u].As().FloatValue(); - const auto bottomLeft = radiiArray[3u].As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRectVarying = {x, y, width, height, topLeft, topRight, bottomRight, bottomLeft}; - AppendCommand(P2D_ROUNDRECTVARYING, args); - } - else - { - throw Napi::Error::New(info.Env(), "Invalid number of parameters for radii"); - } - } - // DOMPoint - // TODO: move duplicate Path2D & Context args parsing into a utils.cpp - else if (radii.IsObject()) - { - const auto dompoint = radii.As(); - const auto radiusX = dompoint.Get("x").As().FloatValue(); - const auto radiusY = dompoint.Get("y").As().FloatValue(); - Path2DCommandArgs args = {}; - args.roundRectElliptic = {x, y, width, height, radiusX, radiusY, radiusX, radiusY, radiusX, radiusY, radiusX, radiusY}; - AppendCommand(P2D_ROUNDRECTELLIPTIC, args); - } - else - { - throw Napi::Error::New(info.Env(), "Invalid radii parameter"); - } - } -} +#include +#include +#include +#include "Canvas.h" +#include "NativeInstanceRegistry.h" +#include "Path2D.h" +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +#define NANOSVG_IMPLEMENTATION // Expands implementation +#include "nanosvg.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +namespace Babylon::Polyfills::Internal +{ + static constexpr auto JS_PATH2D_CONSTRUCTOR_NAME = "Path2D"; + + void NativeCanvasPath2D::Initialize(Napi::Env env) + { + Napi::HandleScope scope{env}; + + Napi::Function func = DefineClass( + env, + JS_PATH2D_CONSTRUCTOR_NAME, + { + InstanceMethod("addPath", &NativeCanvasPath2D::AddPath), + InstanceMethod("closePath", &NativeCanvasPath2D::ClosePath), + InstanceMethod("moveTo", &NativeCanvasPath2D::MoveTo), + InstanceMethod("lineTo", &NativeCanvasPath2D::LineTo), + InstanceMethod("bezierCurveTo", &NativeCanvasPath2D::BezierCurveTo), + InstanceMethod("quadraticCurveTo", &NativeCanvasPath2D::QuadraticCurveTo), + InstanceMethod("arc", &NativeCanvasPath2D::Arc), + InstanceMethod("arcTo", &NativeCanvasPath2D::ArcTo), + InstanceMethod("ellipse", &NativeCanvasPath2D::Ellipse), + InstanceMethod("rect", &NativeCanvasPath2D::Rect), + InstanceMethod("roundRect", &NativeCanvasPath2D::RoundRect), + }); + + JsRuntime::NativeObject::GetFromJavaScript(env).Set(JS_PATH2D_CONSTRUCTOR_NAME, func); + + // Portable Babylon.js paths construct Path2D through the global. + auto global = env.Global(); + + // Install our constructor unconditionally: this Context cannot draw foreign native wraps. + global.Set(JS_PATH2D_CONSTRUCTOR_NAME, func); + } + + bool NativeCanvasPath2D::IsInstance(Napi::Env env, const Napi::Value& value) + { + return TryUnwrap(env, value) != nullptr; + } + + NativeCanvasPath2D* NativeCanvasPath2D::TryUnwrap(Napi::Env env, const Napi::Value& value) + { + return NativeInstanceRegistry::TryUnwrap(env, value); + } + + NativeCanvasPath2D::NativeCanvasPath2D(const Napi::CallbackInfo& info) + : Napi::ObjectWrap{info} + , m_commands{std::deque()} + { + // Path2D | DOMString union: only a real Path2D is copied; everything else stringifies. + const NativeCanvasPath2D* path = info.Length() >= 1 + ? NativeCanvasPath2D::TryUnwrap(info.Env(), info[0]) + : nullptr; + const std::string d = path == nullptr && info.Length() >= 1 && !info[0].IsUndefined() + ? info[0].ToString().Utf8Value() + : ""; + + if (path != nullptr) + { + for (const auto& command : *path) + { + m_commands.push_back(command); + } + } + + if (!d.empty()) + { + NSVGparser* parser = nsvg__createParser(); + const char* path[] = {"d", d.c_str(), NULL}; + const char** attr = {path}; + + assert(strcmp(attr[0], "d") == 0); + assert(!attr[2]); // nsvg__parsePath terminates attr parsing on falsy + + nsvg__parsePath(parser, attr); + + for (NSVGshape *shape = parser->image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + + auto x0 = p[0]; // start x, same as end x of previous + auto y0 = p[1]; // start y, same as end y of previous + auto cpx1 = p[2]; + auto cpy1 = p[3]; + auto cpx2 = p[4]; + auto cpy2 = p[5]; + auto x1 = p[6]; // end x + auto y1 = p[7]; // end y + + // Only need to move on new shape + if (i == 0) + { + Path2DCommandArgs moveArgs = {}; + moveArgs.moveTo = {x0, y0}; + AppendCommand(P2D_MOVETO, moveArgs); + } + + Path2DCommandArgs args = {}; + args.bezierTo = {cpx1, cpy1, cpx2, cpy2, x1, y1}; + AppendCommand(P2D_BEZIERTO, args); + } + } + } + + nsvg__deleteParser(parser); + } + + // Register after successful construction only. + NativeInstanceRegistry::Add(info, this); + } + + NativeCanvasPath2D::~NativeCanvasPath2D() + { + NativeInstanceRegistry::Remove(this); + } + + typename std::deque::iterator NativeCanvasPath2D::begin() + { + return m_commands.begin(); + } + + typename std::deque::iterator NativeCanvasPath2D::end() + { + return m_commands.end(); + } + + typename std::deque::const_iterator NativeCanvasPath2D::begin() const + { + return m_commands.begin(); + } + + typename std::deque::const_iterator NativeCanvasPath2D::end() const + { + return m_commands.end(); + } + + void NativeCanvasPath2D::AppendCommand(Path2DCommandTypes type, Path2DCommandArgs args) + { + m_commands.push_back({type, args}); + } + + void NativeCanvasPath2D::AddPath(const Napi::CallbackInfo& info) + { + if (info.Length() < 1) + { + throw Napi::TypeError::New(info.Env(), "Path2D.addPath: requires at least 1 argument (path)."); + } + + NativeCanvasPath2D* const path = NativeCanvasPath2D::TryUnwrap(info.Env(), info[0]); + if (path == nullptr) + { + throw Napi::TypeError::New(info.Env(), "Path2D.addPath: the first argument is not a Path2D."); + } + + // optional transform arg + bool xformInvReady{false}; + float xformInv[6]; + if (info.Length() == 2) + { + Napi::Object transform = info[1].As(); + auto a = transform.Get("a").As().FloatValue(); + auto b = transform.Get("b").As().FloatValue(); + auto c = transform.Get("c").As().FloatValue(); + auto d = transform.Get("d").As().FloatValue(); + auto e = transform.Get("e").As().FloatValue(); + auto f = transform.Get("f").As().FloatValue(); + + float xform[6] = {a, b, c, d, e, f}; + nsvg__xformInverse(xformInv, xform); + xformInvReady = true; + + Path2DCommandArgs args = {}; + args.transform = { xform[0], xform[1], xform[2], xform[3], xform[4], xform[5] }; + AppendCommand(P2D_TRANSFORM, args); + } + + for (const auto& command : *path) + { + m_commands.push_back(command); + } + + // invert transform after all commands played + if (info.Length() == 2) + { + assert(xformInvReady); + Path2DCommandArgs argsInv = {}; + argsInv.transform = {xformInv[0], xformInv[1], xformInv[2], xformInv[3], xformInv[4], xformInv[5]}; + AppendCommand(P2D_TRANSFORM, argsInv); + } + } + + void NativeCanvasPath2D::ClosePath(const Napi::CallbackInfo& info) + { + AppendCommand(P2D_CLOSE, {}); + } + + void NativeCanvasPath2D::MoveTo(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.moveTo = {x, y}; + AppendCommand(P2D_MOVETO, args); + } + + void NativeCanvasPath2D::LineTo(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.lineTo = {x, y}; + AppendCommand(P2D_LINETO, args); + } + + void NativeCanvasPath2D::BezierCurveTo(const Napi::CallbackInfo& info) + { + const auto cp1x = static_cast(info[0].As().DoubleValue()); + const auto cp1y = static_cast(info[1].As().DoubleValue()); + const auto cp2x = static_cast(info[2].As().DoubleValue()); + const auto cp2y = static_cast(info[3].As().DoubleValue()); + const auto x = static_cast(info[4].As().DoubleValue()); + const auto y = static_cast(info[5].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.bezierTo = {cp1x, cp1y, cp2x, cp2y, x, y}; + AppendCommand(P2D_BEZIERTO, args); + } + + void NativeCanvasPath2D::QuadraticCurveTo(const Napi::CallbackInfo& info) + { + const auto cpx = static_cast(info[0].As().DoubleValue()); + const auto cpy = static_cast(info[1].As().DoubleValue()); + const auto x = static_cast(info[2].As().DoubleValue()); + const auto y = static_cast(info[3].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.quadTo = {cpx, cpy, x, y}; + AppendCommand(P2D_QUADTO, args); + } + + void NativeCanvasPath2D::Arc(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + const auto radius = static_cast(info[2].As().DoubleValue()); + const auto startAngle = static_cast(info[3].As().DoubleValue()); + const auto endAngle = static_cast(info[4].As().DoubleValue()); + const auto counterclockwise = info.Length() == 6 ? info[5].As() : false; + + Path2DCommandArgs args = {}; + args.arc = {x, y, radius, startAngle, endAngle, counterclockwise}; + AppendCommand(P2D_ARC, args); + } + + void NativeCanvasPath2D::ArcTo(const Napi::CallbackInfo& info) + { + const auto x1 = static_cast(info[0].As().DoubleValue()); + const auto y1 = static_cast(info[1].As().DoubleValue()); + const auto x2 = static_cast(info[2].As().DoubleValue()); + const auto y2 = static_cast(info[3].As().DoubleValue()); + const auto radius = static_cast(info[4].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.arcTo = {x1, y1, x2, y2, radius}; + AppendCommand(P2D_ARCTO, args); + } + + void NativeCanvasPath2D::Ellipse(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + const auto radiusX = static_cast(info[2].As().DoubleValue()); + const auto radiusY = static_cast(info[3].As().DoubleValue()); + const auto rotation = static_cast(info[4].As().DoubleValue()); + const auto startAngle = static_cast(info[5].As().DoubleValue()); + const auto endAngle = static_cast(info[6].As().DoubleValue()); + const auto counterclockwise = info.Length() == 8 ? info[7].As() : false; + + Path2DCommandArgs args = {}; + args.ellipse = {x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise}; + AppendCommand(P2D_ELLIPSE, args); + } + + void NativeCanvasPath2D::Rect(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + const auto width = static_cast(info[2].As().DoubleValue()); + const auto height = static_cast(info[3].As().DoubleValue()); + + Path2DCommandArgs args = {}; + args.rect = {x, y, width, height}; + AppendCommand(P2D_RECT, args); + } + + void NativeCanvasPath2D::RoundRect(const Napi::CallbackInfo& info) + { + const auto x = static_cast(info[0].As().DoubleValue()); + const auto y = static_cast(info[1].As().DoubleValue()); + const auto width = static_cast(info[2].As().DoubleValue()); + const auto height = static_cast(info[3].As().DoubleValue()); + const auto radii = info[4]; + + if (radii.IsNumber()) + { + const auto radius = radii.As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRect = {x, y, width, height, radius}; + AppendCommand(P2D_ROUNDRECT, args); + } + else if (radii.IsArray()) + { + const auto radiiArray = radii.As(); + const auto radiiArrayLength = radiiArray.Length(); + if (radiiArrayLength == 1) + { + const auto radius = radiiArray[0u].As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRect = {x, y, width, height, radius}; + AppendCommand(P2D_ROUNDRECT, args); + } + else if (radiiArrayLength == 2) + { + const auto topLeftBottomRight = radiiArray[0u].As().FloatValue(); + const auto topRightBottomLeft = radiiArray[1u].As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRectVarying = {x, y, width, height, topLeftBottomRight, topRightBottomLeft, topLeftBottomRight, topRightBottomLeft}; + AppendCommand(P2D_ROUNDRECTVARYING, args); + } + else if (radiiArrayLength == 3) + { + const auto topLeft = radiiArray[0u].As().FloatValue(); + const auto topRightBottomLeft = radiiArray[1u].As().FloatValue(); + const auto bottomRight = radiiArray[2u].As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRectVarying = {x, y, width, height, topLeft, topRightBottomLeft, bottomRight, topRightBottomLeft}; + AppendCommand(P2D_ROUNDRECTVARYING, args); + } + else if (radiiArrayLength == 4) + { + const auto topLeft = radiiArray[0u].As().FloatValue(); + const auto topRight = radiiArray[1u].As().FloatValue(); + const auto bottomRight = radiiArray[2u].As().FloatValue(); + const auto bottomLeft = radiiArray[3u].As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRectVarying = {x, y, width, height, topLeft, topRight, bottomRight, bottomLeft}; + AppendCommand(P2D_ROUNDRECTVARYING, args); + } + else + { + throw Napi::Error::New(info.Env(), "Invalid number of parameters for radii"); + } + } + // DOMPoint + // TODO: move duplicate Path2D & Context args parsing into a utils.cpp + else if (radii.IsObject()) + { + const auto dompoint = radii.As(); + const auto radiusX = dompoint.Get("x").As().FloatValue(); + const auto radiusY = dompoint.Get("y").As().FloatValue(); + Path2DCommandArgs args = {}; + args.roundRectElliptic = {x, y, width, height, radiusX, radiusY, radiusX, radiusY, radiusX, radiusY, radiusX, radiusY}; + AppendCommand(P2D_ROUNDRECTELLIPTIC, args); + } + else + { + throw Napi::Error::New(info.Env(), "Invalid radii parameter"); + } + } +} diff --git a/Polyfills/Canvas/Source/Path2D.h b/Polyfills/Canvas/Source/Path2D.h index 9f00bb558..870cdd855 100644 --- a/Polyfills/Canvas/Source/Path2D.h +++ b/Polyfills/Canvas/Source/Path2D.h @@ -65,8 +65,14 @@ namespace Babylon::Polyfills::Internal public: static void Initialize(Napi::Env env); + // Validate the native brand before unwrapping. + static bool IsInstance(Napi::Env env, const Napi::Value& value); + + // Returns the wrapped path, or nullptr when `value` is not one of ours. + static NativeCanvasPath2D* TryUnwrap(Napi::Env env, const Napi::Value& value); + explicit NativeCanvasPath2D(const Napi::CallbackInfo& info); - // virtual ~NativeCanvasPath2D(); // TODO: destructor? empty queue? + ~NativeCanvasPath2D(); typename std::deque::iterator begin(); typename std::deque::iterator end(); From 0a5cbdc6ffc395b6164c360afb1cbdc1ebc88fd2 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 15:31:45 -0700 Subject: [PATCH 04/16] Reset strokeRect cached path geometry Begin a fresh NanoVG path before constructing a strokeRect rectangle so cached geometry from an earlier operation is not stroked again. Add pixel regressions covering repeated rectangles and interaction with preceding drawing operations. Review-Group: N1 Source: Review branch cf56bbd7203745055bc43a2b5ea80f5e280e524d Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: b22c311cdca10e3528688fbd68611ffc1dc2b46e Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 71 +++++++++++++++++++ Polyfills/Canvas/Source/Context.cpp | 1 + 2 files changed, 72 insertions(+) diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index 93a03d711..c6eb2033a 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -394,6 +394,77 @@ describe("Canvas2D", function () { ]; } + itWithGpu("uses strokeRect geometry after a preceding fillRect", function () { + const resource = createCanvas(16, 16); + try { + const ctx = resource.context; + ctx.fillStyle = "blue"; + ctx.fillRect(2, 2, 12, 12); + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + ctx.strokeRect(2.5, 2.5, 11, 11); + + const pixels = captureGpuPixels(resource.canvas); + expect(pixelAt(pixels, 16, 8, 1), "outside inset border") + .to.deep.equal([0, 0, 0, 0]); + expect(pixelAt(pixels, 16, 8, 2), "pixel-aligned inset border") + .to.deep.equal([255, 255, 255, 255]); + expect(pixelAt(pixels, 16, 8, 3), "fill inside border") + .to.deep.equal([0, 0, 255, 255]); + } finally { + disposeCanvas(resource); + } + }); + + itWithGpu("does not restroke earlier rectangles with a later strokeRect", function () { + const resource = createCanvas(32, 16); + try { + const ctx = resource.context; + ctx.lineWidth = 1; + ctx.strokeStyle = "red"; + ctx.strokeRect(2.5, 2.5, 9, 11); + ctx.strokeStyle = "blue"; + ctx.strokeRect(20.5, 2.5, 9, 11); + + const pixels = captureGpuPixels(resource.canvas); + expect(pixelAt(pixels, 32, 2, 8), "first rectangle retains its stroke") + .to.deep.equal([255, 0, 0, 255]); + expect(pixelAt(pixels, 32, 20, 8), "second rectangle is drawn") + .to.deep.equal([0, 0, 255, 255]); + expect(pixelAt(pixels, 32, 16, 8), "gap remains untouched") + .to.deep.equal([0, 0, 0, 0]); + } finally { + disposeCanvas(resource); + } + }); + + itWithGpu("preserves a rectangular clip while resetting strokeRect geometry", function () { + const resource = createCanvas(32, 16); + try { + const ctx = resource.context; + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, 16, 16); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 32, 16); + ctx.lineWidth = 1; + ctx.strokeStyle = "white"; + ctx.strokeRect(2.5, 2.5, 27, 11); + ctx.restore(); + + const pixels = captureGpuPixels(resource.canvas); + expect(pixelAt(pixels, 32, 2, 8), "stroke inside clip") + .to.deep.equal([255, 255, 255, 255]); + expect(pixelAt(pixels, 32, 8, 8), "fill inside clip") + .to.deep.equal([0, 0, 255, 255]); + expect(pixelAt(pixels, 32, 29, 8), "stroke outside clip") + .to.deep.equal([0, 0, 0, 0]); + } finally { + disposeCanvas(resource); + } + }); + (skipCanvasGpuTests ? it.skip : it)( "intersects nested clips and restores parent clips on the GPU", async function () { diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 11b1b0159..48c54be73 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -597,6 +597,7 @@ namespace Babylon::Polyfills::Internal const auto width = info[2].As().FloatValue(); const auto height = info[3].As().FloatValue(); + ResetPathState(); nvgRect(*m_nvg, left, top, width, height); BindStrokeStyle(info); SetFilterStack(); From 50ed52779d3c44b98cb19fc1824a8c21aac4426b Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:06 -0700 Subject: [PATCH 05/16] Track raster MSAA state per framebuffer Derive raster multisampling from the currently bound framebuffer rather than the window's sample count. Carry sample metadata through default, offscreen, Canvas, and filter targets so switching targets does not leak MSAA state. Expose validation sample control and add a framebuffer-state regression. Review-Group: N2 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: c7a5b655d78734a143b17dad5afc76a7c6b5b87f Split-Part: code --- Apps/UnitTests/CMakeLists.txt | 2 + .../Source/Tests.Device.FrameEncoder.cpp | 63 +++++++++++++++++++ .../Babylon/Graphics/DeviceContext.h | 7 ++- .../Babylon/Graphics/FrameBuffer.h | 4 +- Core/Graphics/Source/DeviceContext.cpp | 10 +++ Core/Graphics/Source/DeviceImpl.cpp | 7 +++ Core/Graphics/Source/DeviceImpl.h | 1 + Core/Graphics/Source/FrameBuffer.cpp | 9 ++- Plugins/NativeEngine/Source/NativeEngine.cpp | 8 ++- Plugins/TestUtils/Source/TestUtils.cpp | 38 ++++++++++- Plugins/TestUtils/Source/TestUtils.h | 2 + Polyfills/Canvas/Source/Canvas.cpp | 2 +- Polyfills/Canvas/Source/FrameBufferPool.cpp | 2 +- .../Source/nanovg/nanovg_filterstack.cpp | 7 ++- .../Canvas/Source/nanovg/nanovg_filterstack.h | 3 +- 15 files changed, 150 insertions(+), 15 deletions(-) diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index 685fa192c..b7631a933 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -110,6 +110,8 @@ target_link_libraries(UnitTests PRIVATE Window PRIVATE XMLHttpRequest PRIVATE gtest_main + PRIVATE bimg_decode + PRIVATE bimg_encode PRIVATE bimg ${ADDITIONAL_LIBRARIES}) diff --git a/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp b/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp index c15b699cc..c04303001 100644 --- a/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp +++ b/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp @@ -3,6 +3,10 @@ #include #include #include +#include +#ifdef HAS_TEST_UTILS +#include +#endif #include #include @@ -77,6 +81,65 @@ TEST(Device, ActiveEncoderIsNullOutsideFrame) "dereferencing it is unsafe"; } +TEST(Device, FrameBufferMultisamplingTracksItsOwnTarget) +{ + auto config = g_deviceConfig; + config.MSAASamples = 4; + Babylon::Graphics::Device device{config}; + device.StartRenderingCurrentFrame(); + Babylon::AppRuntime runtime{}; + RunOnRuntimeThread(runtime, [&device](Napi::Env env) { + device.AddToJavaScript(env); + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + Babylon::Graphics::FrameBuffer backBuffer{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; + Babylon::Graphics::FrameBuffer singleSample{context, BGFX_INVALID_HANDLE, 64, 64, false, true, true}; + Babylon::Graphics::FrameBuffer multisample{context, BGFX_INVALID_HANDLE, 64, 64, false, true, true, -1, true}; + const auto xrHandle = bgfx::createFrameBuffer(64, 64, bgfx::TextureFormat::RGBA8); + ASSERT_TRUE(bgfx::isValid(xrHandle)); + Babylon::Graphics::FrameBuffer xrBackBuffer{context, xrHandle, 64, 64, true, false, false}; + EXPECT_EQ(context.GetMSAASamples(), 4); + EXPECT_TRUE(backBuffer.IsMultisampled()); +#ifdef HAS_TEST_UTILS + Babylon::Plugins::TestUtils::Initialize(env, g_deviceConfig.Window); + const auto testUtils = env.Global().Get("TestUtils").As(); + const auto setSamples = testUtils.Get("setMSAASamples").As(); +#endif + for (const uint8_t samples : {0, 1, 2, 4, 8, 16}) + { +#ifdef HAS_TEST_UTILS + setSamples.Call(testUtils, {Napi::Number::New(env, samples)}); +#else + context.UpdateMSAA(samples); +#endif + EXPECT_EQ(context.GetMSAASamples(), samples == 0 ? 1 : samples); + EXPECT_EQ(backBuffer.IsMultisampled(), samples > 1); + EXPECT_FALSE(singleSample.IsMultisampled()); + EXPECT_TRUE(multisample.IsMultisampled()); + EXPECT_FALSE(xrBackBuffer.IsMultisampled()); + } +#ifdef HAS_TEST_UTILS + const auto rejectsInvalid = Napi::Eval(env, R"( + (function() { + const invalid = [-1, 3, 4.5, 256, NaN, Infinity, undefined, null, "4", + true, false, {}, [], new Number(4)]; + return invalid.every(function(value) { + try { + TestUtils.setMSAASamples(value); + return false; + } catch (error) { + return error instanceof Error; + } + }); + })() + )", "validation-msaa-options.js"); + EXPECT_TRUE(rejectsInvalid.As().Value()); + EXPECT_THROW(setSamples.Call(testUtils, {}), Napi::Error); + EXPECT_EQ(context.GetMSAASamples(), 16); +#endif + }); + device.FinishRenderingCurrentFrame(); +} + // The guarantee the fix relies on: acquiring a FrameCompletionScope blocks until a frame is live, // and once acquired the encoder is valid and cannot be ended underneath the holder. TEST(Device, FrameCompletionScopeProvidesEncoderOutsideFrame) diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h index 865eb8e1d..73ea0bd93 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h @@ -108,6 +108,8 @@ namespace Babylon::Graphics float GetHardwareScalingLevel(); void SetHardwareScalingLevel(float level); + void UpdateMSAA(uint8_t samples); + uint8_t GetMSAASamples() const; size_t GetWidth() const; size_t GetHeight() const; @@ -133,9 +135,8 @@ namespace Babylon::Graphics // continue within the same logical frame. Call at draw/clear op boundaries. void FlushViewsIfNeeded(); - // Force a mid-frame flush when a FrameCompletionScope is held so pending - // bgfx::readTexture requests can complete (Canvas toDataURL / drawImage). - // Returns false when the render thread cannot service the request. + // Flush pending compute writes or readbacks while a FrameCompletionScope is + // held. Returns false when the render thread cannot service the request. bool ForceMidFrameFlush(); // TODO: find a different way to get the texture info for frame capture diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h index e6c33d455..a2b478a16 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h @@ -20,7 +20,7 @@ namespace Babylon::Graphics class FrameBuffer final { public: - FrameBuffer(DeviceContext& context, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex = -1); + FrameBuffer(DeviceContext& context, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex = -1, bool isMultisampled = false); ~FrameBuffer(); FrameBuffer(const FrameBuffer&) = delete; @@ -32,6 +32,7 @@ namespace Babylon::Graphics uint16_t Width() const; uint16_t Height() const; bool DefaultBackBuffer() const; + bool IsMultisampled() const; void Bind(); void Unbind(); @@ -60,6 +61,7 @@ namespace Babylon::Graphics const bool m_useDeviceBackBuffer{}; const bool m_hasDepth{}; const bool m_hasStencil{}; + const bool m_isMultisampled{}; std::optional m_viewId{}; diff --git a/Core/Graphics/Source/DeviceContext.cpp b/Core/Graphics/Source/DeviceContext.cpp index d57dcffcf..3c75df894 100644 --- a/Core/Graphics/Source/DeviceContext.cpp +++ b/Core/Graphics/Source/DeviceContext.cpp @@ -107,6 +107,16 @@ namespace Babylon::Graphics m_graphicsImpl.SetHardwareScalingLevel(level); } + void DeviceContext::UpdateMSAA(uint8_t samples) + { + m_graphicsImpl.UpdateMSAA(samples); + } + + uint8_t DeviceContext::GetMSAASamples() const + { + return m_graphicsImpl.GetMSAASamples(); + } + size_t DeviceContext::GetWidth() const { return m_graphicsImpl.GetWidth(); diff --git a/Core/Graphics/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index 24d66a3cd..2bd1e293b 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -209,6 +209,13 @@ namespace Babylon::Graphics m_state.Bgfx.Dirty = true; } + uint8_t DeviceImpl::GetMSAASamples() const + { + std::scoped_lock lock{m_state.Mutex}; + const auto level = (m_state.Bgfx.InitState.swapChain.flags & BGFX_SWAP_CHAIN_MSAA_MASK) >> BGFX_SWAP_CHAIN_MSAA_SHIFT; + return static_cast(1u << level); + } + void DeviceImpl::UpdateAlphaPremultiplied(bool enabled) { std::scoped_lock lock{m_state.Mutex}; diff --git a/Core/Graphics/Source/DeviceImpl.h b/Core/Graphics/Source/DeviceImpl.h index 68f5f7ab0..b9c16b5e5 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -48,6 +48,7 @@ namespace Babylon::Graphics void UpdateDevice(DeviceT device); void UpdateSize(size_t width, size_t height); void UpdateMSAA(uint8_t value); + uint8_t GetMSAASamples() const; void UpdateAlphaPremultiplied(bool enabled); #ifdef GRAPHICS_BACK_BUFFER_SUPPORT diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index ac9fba8dc..2a90297d4 100644 --- a/Core/Graphics/Source/FrameBuffer.cpp +++ b/Core/Graphics/Source/FrameBuffer.cpp @@ -5,7 +5,7 @@ namespace Babylon::Graphics { - FrameBuffer::FrameBuffer(DeviceContext& deviceContext, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex) + FrameBuffer::FrameBuffer(DeviceContext& deviceContext, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex, bool isMultisampled) : m_deviceContext{deviceContext} , m_deviceID{deviceContext.GetDeviceId()} , m_handle{handle} @@ -16,6 +16,7 @@ namespace Babylon::Graphics , m_useDeviceBackBuffer{defaultBackBuffer && !bgfx::isValid(handle)} , m_hasDepth{hasDepth} , m_hasStencil{hasStencil} + , m_isMultisampled{isMultisampled} , m_disposed{false} , m_depthStencilAttachmentIndex{depthStencilAttachmentIndex} { @@ -71,6 +72,12 @@ namespace Babylon::Graphics return m_defaultBackBuffer; } + bool FrameBuffer::IsMultisampled() const + { + // The window's sample count can change without recreating its wrapper. + return m_defaultBackBuffer && !bgfx::isValid(m_handle) ? m_deviceContext.GetMSAASamples() > 1 : m_isMultisampled; + } + void FrameBuffer::Bind() { m_viewId.reset(); diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 58306d30d..cf336e79f 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -3284,13 +3284,17 @@ namespace Babylon } auto& boundFrameBuffer = GetBoundFrameBuffer(); + // D3D11 also uses this flag to select its line coverage algorithm on single-sample targets. + const uint64_t multisampleMask = boundFrameBuffer.IsMultisampled() ? UINT64_MAX : ~BGFX_STATE_MSAA; if (boundFrameBuffer.HasDepth()) { - encoder->setState(m_engineState | fillModeState); + const uint64_t drawState = (m_engineState | fillModeState); + encoder->setState(drawState & multisampleMask); } else { - encoder->setState((m_engineState & ~BGFX_STATE_WRITE_Z) | fillModeState); + const uint64_t drawState = ((m_engineState & ~BGFX_STATE_WRITE_Z) | fillModeState); + encoder->setState(drawState & multisampleMask); } boundFrameBuffer.SetStencil(*encoder, m_stencilState); diff --git a/Plugins/TestUtils/Source/TestUtils.cpp b/Plugins/TestUtils/Source/TestUtils.cpp index fb1248a37..d9508d18a 100644 --- a/Plugins/TestUtils/Source/TestUtils.cpp +++ b/Plugins/TestUtils/Source/TestUtils.cpp @@ -27,6 +27,20 @@ namespace Babylon::Plugins::Internal return Napi::Value::From(info.Env(), STRINGIZE(GRAPHICS_API)); } + void TestUtils::SetMSAASamples(const Napi::CallbackInfo& info) + { + if (info.Length() == 0 || !info[0].IsNumber()) + { + throw Napi::TypeError::New(info.Env(), "MSAA samples must be a number."); + } + const auto samples = info[0].As().DoubleValue(); + if (samples != 0 && samples != 1 && samples != 2 && samples != 4 && samples != 8 && samples != 16) + { + throw Napi::RangeError::New(info.Env(), "MSAA samples must be 0, 1, 2, 4, 8, or 16."); + } + m_deviceContext.UpdateMSAA(static_cast(samples)); + } + void TestUtils::WritePNG(const Napi::CallbackInfo& info) { #ifndef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES @@ -83,9 +97,27 @@ namespace Babylon::Plugins::Internal return info.Env().Undefined(); } - auto data = Napi::Uint8Array::New(info.Env(), imageData->m_Image->m_size); - const auto ptr = static_cast(imageData->m_Image->m_data); - memcpy(data.Data(), ptr, imageData->m_Image->m_size); + // Normalize reference PNGs to RGBA8 to match native screenshots. + const bimg::ImageContainer* src = imageData->m_Image; + const uint32_t width = src->m_width; + const uint32_t height = src->m_height; + const size_t rgbaSize = static_cast(width) * height * 4; + auto data = Napi::Uint8Array::New(info.Env(), rgbaSize); + uint8_t* dst = data.Data(); + const uint8_t* ptr = static_cast(src->m_data); + + if (src->m_format == bimg::TextureFormat::RGBA8) + { + if (src->m_size < rgbaSize) + { + throw Napi::Error::New(info.Env(), "Reference image RGBA8 buffer is smaller than width*height*4."); + } + memcpy(dst, ptr, rgbaSize); + } + else if (!bimg::imageConvert(&Graphics::DeviceContext::GetDefaultAllocator(), dst, bimg::TextureFormat::RGBA8, ptr, src->m_format, width, height, 1)) + { + throw Napi::Error::New(info.Env(), "Failed to convert reference image to RGBA8."); + } return Napi::Value::From(info.Env(), data); #endif diff --git a/Plugins/TestUtils/Source/TestUtils.h b/Plugins/TestUtils/Source/TestUtils.h index 3332c4e23..95a2c058c 100644 --- a/Plugins/TestUtils/Source/TestUtils.h +++ b/Plugins/TestUtils/Source/TestUtils.h @@ -36,6 +36,7 @@ namespace Babylon::Plugins::Internal { ParentT::InstanceMethod("exit", &TestUtils::Exit), ParentT::InstanceMethod("updateSize", &TestUtils::UpdateSize), + ParentT::InstanceMethod("setMSAASamples", &TestUtils::SetMSAASamples), ParentT::InstanceMethod("setTitle", &TestUtils::SetTitle), ParentT::InstanceMethod("getGraphicsApiName", &TestUtils::GetGraphicsApiName), ParentT::InstanceMethod("writePNG", &TestUtils::WritePNG), @@ -68,6 +69,7 @@ namespace Babylon::Plugins::Internal void Exit(const Napi::CallbackInfo& info); void UpdateSize(const Napi::CallbackInfo& info); + void SetMSAASamples(const Napi::CallbackInfo& info); void SetTitle(const Napi::CallbackInfo& info); Napi::Value GetOutputDirectory(const Napi::CallbackInfo& info); diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index 5bff7dc91..c0d4a1d4e 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -224,7 +224,7 @@ namespace Babylon::Polyfills::Internal } throw std::runtime_error{"bgfx::createFrameBuffer returned invalid handle (framebuffer pool exhausted; raise BGFX_CONFIG_MAX_FRAME_BUFFERS or audit Canvas/Context lifetime)"}; } - m_frameBuffer = std::make_unique(m_graphicsContext, handle, m_width, m_height, false, false, false); + m_frameBuffer = std::make_unique(m_graphicsContext, handle, m_width, m_height, false, false, false, -1, false); m_dirty = false; if (m_texture) diff --git a/Polyfills/Canvas/Source/FrameBufferPool.cpp b/Polyfills/Canvas/Source/FrameBufferPool.cpp index d7bfec95b..df51b9ad2 100644 --- a/Polyfills/Canvas/Source/FrameBufferPool.cpp +++ b/Polyfills/Canvas/Source/FrameBufferPool.cpp @@ -80,7 +80,7 @@ namespace Babylon::Polyfills throw std::runtime_error{"FrameBufferPool::Add: bgfx::createFrameBuffer returned invalid handle (pool exhausted)"}; } - FrameBuffer = new Graphics::FrameBuffer(*m_graphicsContext, TextBuffer, m_width, m_height, false, false, false); + FrameBuffer = new Graphics::FrameBuffer(*m_graphicsContext, TextBuffer, m_width, m_height, false, false, false, -1, false); m_available++; mPoolBuffers.push_back({FrameBuffer, true}); } diff --git a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp index 9a19aaa87..948dd64f1 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp +++ b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp @@ -80,6 +80,8 @@ void nanovg_filterstack::DisposeBgfx() return; } + // Destroy only on the last live canvas/NVG context, then clear handles so a + // later InitBgfx starts clean and stale handles are never double-destroyed. if (bgfx::isValid(m_uniforms.u_strength)) { bgfx::destroy(m_uniforms.u_strength); @@ -192,7 +194,8 @@ void nanovg_filterstack::Render( std::function finalPass, Babylon::Graphics::FrameBuffer* finalFrameBuffer, std::function acquire, - std::function release + std::function release, + bool separateComposite ) { if (!stackElementCount) @@ -230,7 +233,7 @@ void nanovg_filterstack::Render( for (int i = 0; i < 2; i++) { const std::array& direction = directions[i]; - bool last = lastElement && i == 1; + bool last = lastElement && i == 1 && !separateComposite; float sigma = i == 0 ? element.blurElement.horizontal : element.blurElement.vertical; // use gaussian blur for sigma < 2, box blur for sigma >= 2 diff --git a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h index 7e987005d..3a4bcf471 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h +++ b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h @@ -40,7 +40,8 @@ class nanovg_filterstack std::function finalPass, Babylon::Graphics::FrameBuffer* finalFrameBuffer, std::function acquire, - std::function release + std::function release, + bool separateComposite = false ); void Render(std::function element); From ad27a08bd75192038348068c682cd46698648a10 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:06 -0700 Subject: [PATCH 06/16] Use bimg for Canvas image I/O and cover SVG sizing Link Canvas directly to bimg for pixel conversion and PNG writing, including builds with image loading disabled, instead of bimg_encode. Use the upstream imageParse path for SVG and bitmap images without a second Canvas-specific parser or size cap. Cover RGBA output, fractional dimensions, the 4096-pixel bound, and invalid dimensions, and retain image onerror callback behavior. Review-Group: N3 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 09a32051c5f9c523dcb9f012aaecc8d4a9cffe57 Split-Part: code --- .../Source/Tests.Canvas.Readback.cpp | 64 +++++++++++++++++++ Polyfills/Canvas/CMakeLists.txt | 4 +- Polyfills/Canvas/Source/Image.cpp | 60 ++++++++--------- Polyfills/Canvas/Source/Image.h | 8 +-- 4 files changed, 94 insertions(+), 42 deletions(-) diff --git a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp index 9756317dd..13d28a3c3 100644 --- a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp +++ b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "../../../Polyfills/Canvas/Source/Canvas.h" #include "../../../Polyfills/Canvas/Source/Context.h" #include "../../../Polyfills/Canvas/Source/Gradient.h" @@ -19,6 +22,7 @@ #include #include #include +#include #include extern Babylon::Graphics::Configuration g_deviceConfig; @@ -123,6 +127,66 @@ TEST(CanvasReadback, NativeBrandsRequireTheOriginalReceiver) }); } +#ifdef HAS_NATIVE_IMAGE_LOADING +TEST(CanvasImages, SvgUsesBimgParserAndRgbaPixels) +{ + bx::DefaultAllocator allocator; + constexpr std::string_view svg{R"()"}; + const std::unique_ptr image{ + bimg::imageParse(&allocator, svg.data(), static_cast(svg.size())), bimg::imageFree}; + ASSERT_NE(image, nullptr); + EXPECT_EQ(image->m_parser, bimg::ImageParser::Svg); + EXPECT_EQ(image->m_format, bimg::TextureFormat::RGBA8); + EXPECT_EQ(image->m_width, 4); + EXPECT_EQ(image->m_height, 2); + ASSERT_EQ(image->m_size, 32); + EXPECT_TRUE(image->m_hasAlpha); + const auto* pixels = static_cast(image->m_data); + EXPECT_EQ(pixels[0], 255); + EXPECT_EQ(pixels[1], 0); + EXPECT_EQ(pixels[2], 0); + EXPECT_EQ(pixels[3], 255); + EXPECT_EQ(pixels[15], 0); +} + +TEST(CanvasImages, SvgUsesBimgDimensionPolicy) +{ + bx::DefaultAllocator allocator; + struct Fixture + { + const char* dimensions; + uint32_t width; + uint32_t height; + }; + for (const auto& fixture : std::array{{ + {"width=\"4.25\" height=\"2.25\"", 4, 2}, + {"width=\"4.5\" height=\"2.5\"", 5, 3}, + {"width=\"64.0005\" height=\"64.0007\"", 64, 64}, + {"width=\"8192\" height=\"2\"", 4096, 1}, + }}) + { + const std::string svg = std::string{""; + const std::unique_ptr image{ + bimg::imageParse(&allocator, svg.data(), static_cast(svg.size())), bimg::imageFree}; + ASSERT_NE(image, nullptr) << fixture.dimensions; + EXPECT_EQ(image->m_parser, bimg::ImageParser::Svg); + EXPECT_EQ(image->m_width, fixture.width); + EXPECT_EQ(image->m_height, fixture.height); + } +} + +TEST(CanvasImages, SvgRejectsInvalidDimensions) +{ + bx::DefaultAllocator allocator; + bx::Error error; + constexpr std::string_view svg{R"()"}; + const std::unique_ptr image{ + bimg::imageParse(&allocator, svg.data(), static_cast(svg.size()), bimg::TextureFormat::Count, &error), bimg::imageFree}; + EXPECT_EQ(image, nullptr); + EXPECT_FALSE(error.isOk()); +} +#endif + TEST(CanvasReadback, DrawImageRejectsArityAndNoOpGeometryBeforeReadbackOrUpload) { RunCanvasTest([](Napi::Env env) { diff --git a/Polyfills/Canvas/CMakeLists.txt b/Polyfills/Canvas/CMakeLists.txt index 22affaf88..909047907 100644 --- a/Polyfills/Canvas/CMakeLists.txt +++ b/Polyfills/Canvas/CMakeLists.txt @@ -53,13 +53,11 @@ target_link_libraries(Canvas PRIVATE GraphicsDeviceContext PRIVATE UrlLib PRIVATE base-n - PRIVATE bimg_encode) + PRIVATE bimg) if(BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES) target_compile_definitions(Canvas PRIVATE BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES) - target_link_libraries(Canvas - PRIVATE bimg) endif() if(SHADERC_PATH AND NOT IOS AND NOT VISIONOS AND NOT ANDROID) diff --git a/Polyfills/Canvas/Source/Image.cpp b/Polyfills/Canvas/Source/Image.cpp index af8489bac..ec17549fd 100644 --- a/Polyfills/Canvas/Source/Image.cpp +++ b/Polyfills/Canvas/Source/Image.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES #include #include @@ -35,8 +36,6 @@ namespace Babylon::Polyfills::Internal InstanceAccessor("naturalWidth", &NativeCanvasImage::GetNaturalWidth, nullptr), InstanceAccessor("naturalHeight", &NativeCanvasImage::GetNaturalHeight, nullptr), InstanceAccessor("src", &NativeCanvasImage::GetSrc, &NativeCanvasImage::SetSrc), - InstanceAccessor("onload", nullptr, &NativeCanvasImage::SetOnload), - InstanceAccessor("onerror", nullptr, &NativeCanvasImage::SetOnerror), // TODO: This should be set directly on the JS Object rather than via an instanceAccessor see: https://github.com/BabylonJS/BabylonNative/issues/1030 InstanceAccessor("_imageContainer", &NativeCanvasImage::GetImageContainer, nullptr), }); @@ -54,6 +53,10 @@ namespace Babylon::Polyfills::Internal , m_runtimeScheduler{JsRuntime::GetFromJavaScript(info.Env())} , m_cancellationSource{std::make_shared()} { + // Keep callback cycles visible to the JavaScript garbage collector. + auto self = info.This().As(); + self.Set("onload", info.Env().Null()); + self.Set("onerror", info.Env().Null()); // Register after successful construction only. NativeInstanceRegistry::Add(info, this); } @@ -117,7 +120,7 @@ namespace Babylon::Polyfills::Internal return Env().Null(); } - bool NativeCanvasImage::SetBuffer(gsl::span buffer) + bool NativeCanvasImage::SetBuffer(const Napi::Object& self, gsl::span buffer) { #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES ReleaseImage(); @@ -133,7 +136,6 @@ namespace Babylon::Polyfills::Internal bimg::imageFree(source); } } - if (m_imageContainer == nullptr) { return false; @@ -144,12 +146,14 @@ namespace Babylon::Polyfills::Internal // Bump before onload. A draw in that callback must not reuse the previous texture. ++m_contentGeneration; - if (!m_onloadHandlerRef.IsEmpty()) + const auto onload = self.Get("onload"); + if (onload.IsFunction()) { - m_onloadHandlerRef.Call({}); + onload.As().Call(self, {}); } return true; #else + (void)self; (void)buffer; return false; #endif @@ -159,7 +163,7 @@ namespace Babylon::Polyfills::Internal { #ifndef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES (void)value; - HandleLoadImageError(Napi::Error::New(info.Env(), "Image loading is disabled in this build (BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES=OFF).")); + HandleLoadImageError(info.This().As(), Napi::Error::New(info.Env(), "Image loading is disabled in this build (BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES=OFF).")); return; #else auto text{value.As().Utf8Value()}; @@ -174,7 +178,7 @@ namespace Babylon::Polyfills::Internal { // SetSrc, disposal, decoding and event delivery share the JS runtime thread; // cancellation cannot interleave with this synchronous decode. - arcana::make_task(m_runtimeScheduler, *m_cancellationSource, [env{info.Env()}, this, cancellationSource{m_cancellationSource}, text{std::move(text)}, pos]() { + arcana::make_task(m_runtimeScheduler, *m_cancellationSource, [image{Napi::Persistent(info.This().As())}, this, cancellationSource{m_cancellationSource}, text{std::move(text)}, pos]() { if (cancellationSource->cancelled()) { return; @@ -183,9 +187,10 @@ namespace Babylon::Polyfills::Internal bn::decode_b64(text.begin() + pos + base64.length(), text.end(), std::back_inserter(base64Buffer)); gsl::span buffer = {reinterpret_cast(base64Buffer.data()), base64Buffer.size()}; - if (!SetBuffer(buffer)) + const auto self = image.Value(); + if (!SetBuffer(self, buffer)) { - HandleLoadImageError(Napi::Error::New(env, "Unable to decode image with provided base64 source.")); + HandleLoadImageError(self, Napi::Error::New(image.Env(), "Unable to decode image with provided base64 source.")); } }); return; @@ -195,44 +200,33 @@ namespace Babylon::Polyfills::Internal UrlLib::UrlRequest request{}; request.Open(UrlLib::UrlMethod::Get, text); request.ResponseType(UrlLib::UrlResponseType::Buffer); - request.SendAsync().then(m_runtimeScheduler, *m_cancellationSource, [env{info.Env()}, this, cancellationSource{m_cancellationSource}, request{std::move(request)}](arcana::expected result) { + request.SendAsync().then(m_runtimeScheduler, *m_cancellationSource, [image{Napi::Persistent(info.This().As())}, this, cancellationSource{m_cancellationSource}, request{std::move(request)}](arcana::expected result) { if (cancellationSource->cancelled()) { return; } + const auto self = image.Value(); if (result.has_error()) { - HandleLoadImageError(Napi::Error::New(env, result.error())); + HandleLoadImageError(self, Napi::Error::New(image.Env(), result.error())); return; } auto buffer{request.ResponseBuffer()}; if (buffer.data() == nullptr || buffer.size_bytes() == 0) { - HandleLoadImageError(Napi::Error::New(env, "Image with provided source returned empty response or invalid base64.")); + HandleLoadImageError(self, Napi::Error::New(image.Env(), "Image with provided source returned empty response or invalid base64.")); return; } - if (!SetBuffer(buffer)) + if (!SetBuffer(self, buffer)) { - HandleLoadImageError(Napi::Error::New(env, "Unable to decode image with provided source URL.")); + HandleLoadImageError(self, Napi::Error::New(image.Env(), "Unable to decode image with provided source URL.")); } }); #endif } - void NativeCanvasImage::SetOnload(const Napi::CallbackInfo&, const Napi::Value& value) - { - Napi::Function eventHandler{value.As()}; - m_onloadHandlerRef = Napi::Persistent(eventHandler); - } - - void NativeCanvasImage::SetOnerror(const Napi::CallbackInfo&, const Napi::Value& value) - { - Napi::Function eventHandler{value.As()}; - m_onerrorHandlerRef = Napi::Persistent(eventHandler); - } - int NativeCanvasImage::CreateNVGImageForContext(NVGcontext* nvgContext) const { #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES @@ -243,14 +237,14 @@ namespace Babylon::Polyfills::Internal #endif } - void NativeCanvasImage::HandleLoadImageError(const Napi::Error& error) + void NativeCanvasImage::HandleLoadImageError(const Napi::Object& self, const Napi::Error& error) { - if (!m_onerrorHandlerRef.IsEmpty()) + // Match HTML : fire onerror when set; otherwise fail silently. + // Throwing here made GUI image tests flaky (async decode after/during ready). + const auto onerror = self.Get("onerror"); + if (onerror.IsFunction()) { - m_onerrorHandlerRef.Call({error.Value()}); - return; + onerror.As().Call(self, {error.Value()}); } - - error.ThrowAsJavaScriptException(); } } diff --git a/Polyfills/Canvas/Source/Image.h b/Polyfills/Canvas/Source/Image.h index 5c3b28a60..4d02c85d3 100644 --- a/Polyfills/Canvas/Source/Image.h +++ b/Polyfills/Canvas/Source/Image.h @@ -40,10 +40,8 @@ namespace Babylon::Polyfills::Internal Napi::Value GetSrc(const Napi::CallbackInfo&); Napi::Value GetImageContainer(const Napi::CallbackInfo&); void SetSrc(const Napi::CallbackInfo&, const Napi::Value&); - void SetOnload(const Napi::CallbackInfo&, const Napi::Value&); - void SetOnerror(const Napi::CallbackInfo&, const Napi::Value&); - void HandleLoadImageError(const Napi::Error& error); - bool SetBuffer(gsl::span buffer); + void HandleLoadImageError(const Napi::Object& self, const Napi::Error& error); + bool SetBuffer(const Napi::Object& self, gsl::span buffer); void ReleaseImage(); void Dispose(); @@ -54,8 +52,6 @@ namespace Babylon::Polyfills::Internal std::string m_src{}; JsRuntimeScheduler m_runtimeScheduler; - Napi::FunctionReference m_onloadHandlerRef; - Napi::FunctionReference m_onerrorHandlerRef; std::shared_ptr m_cancellationSource{}; #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES bimg::ImageContainer* m_imageContainer{}; From 8fd2b1a37920c535a2de0e395d4e635e1b788ac0 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:11 -0700 Subject: [PATCH 07/16] Normalize shader-visible texture coordinates and pixel uploads Make fragment coordinates, texture sampling/fetches, cube orientation, and raw uploads agree on WebGL's logical row convention. Keep Canvas readback GPU-authoritative without an unused CPU pixel mirror, and include the required path-state helpers. Preserve upstream coordinate regressions; defer shadow and IBL integration cases needing Babylon.js changes. Review-Group: N4 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: fb7d9aeb185c435291f78058dc2aabd39574d64d Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 59 +++++-- .../Tests.ShaderCompilation.FragCoord.cpp | 63 ++++---- Core/Graphics/Source/Texture.cpp | 7 + Plugins/NativeEngine/Source/NativeEngine.cpp | 22 ++- .../Source/ShaderCompilerDXBC.cpp | 2 +- .../Source/ShaderCompilerTraversers.cpp | 151 +++++++++++------- .../Source/ShaderCompilerTraversers.h | 6 +- Polyfills/Canvas/Source/Context.cpp | 50 +++--- Polyfills/Canvas/Source/nanovg/nanovg.cpp | 17 ++ Polyfills/Canvas/Source/nanovg/nanovg.h | 9 ++ 10 files changed, 249 insertions(+), 137 deletions(-) diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index c6eb2033a..bb8bf81a9 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -41,7 +41,6 @@ declare const setExitCode: (code: number) => void; declare const setImageReloadTestResponse: (bytes: Uint8Array) => void; declare const skipCanvasGpuTests: boolean; declare const _native: any; - registerPngTests(describe, it, hasGpuRendering && hasNativeImageLoading); registerAttributeLessInstancingTests(describe, it, hasAttributeLessInstancing); @@ -347,6 +346,22 @@ describe("ColorParsing", function () { }); }); +describe("Native splat matrix storage", function () { + const test = typeof _native.sortSplats === "function" ? it : it.skip; + for (const numberArray of [false, true]) { + test(`sorts with ${numberArray ? "number-array" : "Float32Array"} matrices`, function () { + const values = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + const matrix = { _m: numberArray ? values : new Float32Array(values) }; + const positions = new Float32Array([0, 0, 1, 1, 0, 0, 4, 1, 0, 0, 2, 1]); + for (const rightHanded of [false, true]) { + const indices = new Float32Array(3); + _native.sortSplats(matrix, positions, indices, rightHanded); + expect(Array.from(indices)).to.deep.equal(rightHanded ? [0, 2, 1] : [1, 2, 0]); + } + }); + } +}); + describe("Canvas2D", function () { // No-op renderers accept GPU commands but cannot produce pixels for readback. const itWithGpu = hasGpuRendering ? it : it.skip; @@ -358,6 +373,34 @@ describe("Canvas2D", function () { return canvas.getContext("2d"); } + (skipCanvasGpuTests ? it.skip : it)("uses the strokeRect geometry after a preceding fillRect", async function () { + this.timeout(10000); + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture("fill then inset stroke", 16, scene, false); + const ctx = texture.getContext(); + ctx.fillStyle = "blue"; + ctx.fillRect(2, 2, 12, 12); + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + ctx.strokeRect(2.5, 2.5, 11, 11); + texture.update(false); + + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for the canvas texture"); + } + const pixel = (x: number, y: number) => Array.from(pixels.subarray((y * 16 + x) * 4, (y * 16 + x + 1) * 4)); + expect(pixel(8, 1), "outside inset border").to.deep.equal([0, 0, 0, 0]); + expect(pixel(8, 2), "pixel-aligned inset border").to.deep.equal([255, 255, 255, 255]); + expect(pixel(8, 3), "button fill inside border").to.deep.equal([0, 0, 255, 255]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + function createCanvas(width: number, height: number): any { const canvas = new _native.Canvas(); canvas.width = width; @@ -1708,19 +1751,6 @@ describe("Canvas2D", function () { }); }); - it("returns ascent and descent in no-font text metrics", function () { - const resource = createCanvas(8, 8); - try { - resource.context.font = "20px MissingFontForCanvasMetrics"; - const metrics = resource.context.measureText("test"); - expect(metrics).to.have.property("actualBoundingBoxAscent"); - expect(metrics).to.have.property("actualBoundingBoxDescent"); - expect(metrics.actualBoundingBoxAscent).to.equal(15); - expect(metrics.actualBoundingBoxDescent).to.equal(5); - } finally { - disposeCanvas(resource); - } - }); }); function createSceneAndWait(callback: (engine: NativeEngine, scene: Scene) => void, done: () => void) { @@ -1733,6 +1763,7 @@ function createSceneAndWait(callback: (engine: NativeEngine, scene: Scene) => vo }); } + describe("Materials", function () { this.timeout(0); it("Empty ShaderMaterial should compile", function (done) { diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp index 90532abaa..5e2452b3f 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp @@ -24,11 +24,7 @@ extern Babylon::Graphics::Configuration g_deviceConfig; -// These tests pin down the orientation of gl_FragCoord.y. -// -// D3D, Metal and Vulkan rasterize with a top-left origin while GL uses bottom-left, -// and Babylon Native does not flip geometry, so gl_FragCoord.y arrives mirrored and -// is corrected by the shader compiler (FragCoordYFlipTraverser). +// Shader-visible coordinates use GL's bottom-left origin on every backend. namespace { class TestCompletion @@ -105,6 +101,7 @@ namespace const std::string& setupScript = {}, std::chrono::milliseconds renderTimeout = std::chrono::seconds{30}) { + // Use clip-space geometry and bottom-left-origin UVs. Babylon::Graphics::Device device{g_deviceConfig}; Babylon::Graphics::TextureT outputTexture{}; const auto releaseOutput = gsl::finally([&outputTexture] { @@ -195,8 +192,7 @@ namespace camera.orthoRight = 1; camera.outputRenderTarget = outputTexture; - // Clip-space quad passed straight through the vertex shader, so no - // projection matrix is involved and it lines up with the target exactly. + // Cover the target without a projection matrix. var quad = new BABYLON.Mesh("quad", scene); var vertexData = new BABYLON.VertexData(); vertexData.positions = [ @@ -234,8 +230,7 @@ namespace material.setVector2("targetSize", new BABYLON.Vector2(width, height)); if (WITH_INPUT_TEXTURE) { - // Decreasing red ramp makes a vertical mirror unambiguous; blue - // encodes the low bits of the row index to catch off-by-one errors. + // A red ramp detects mirrors; row-index bits in blue detect off-by-one errors. var data = new Uint8Array(width * height * 4); for (var y = 0; y < height; ++y) { for (var x = 0; x < width; ++x) { @@ -429,6 +424,26 @@ TEST(ShaderCompilation, FragCoordSetupAndPreparationFailuresPropagate) #endif } +TEST(NativeEngineViewport, MatchesClipSpaceRegion) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + const std::string fragmentShader = + "precision highp float;\n" + "void main(void) { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }\n"; + const auto expected = RenderFullScreenQuad(8, 8, + "attribute vec3 position;\n" + "void main(void) { gl_Position = vec4(position.x * 0.5, position.y * 0.5 - 0.25, 0.0, 1.0); }\n", + fragmentShader, false); + const auto actual = RenderFullScreenQuad(8, 8, + "attribute vec3 position;\n" + "void main(void) { gl_Position = vec4(position, 1.0); }\n", + fragmentShader, false, "camera.viewport = new BABYLON.Viewport(0.25, 0.125, 0.5, 0.5);"); + EXPECT_EQ(actual, expected); +#endif +} + TEST(NativeEngineClear, PreservesTextureBindings) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) @@ -664,13 +679,7 @@ TEST(NativeEngineInstanceData, DynamicVertexBufferUpdateWithEmptyStreamDoesNotWa << "an update with no queued commands must not wait for the next frame"; } -// gl_FragCoord.y must increase towards +Y in clip space, like the interpolated vUV.y -// the quad supplies. Without the correction the two ramps become mirror images. -// -// Two channels of a single render are compared rather than absolute row indices -// because Helpers::ReadPixels returns the bottom scanline first on OpenGL and the top -// first on D3D11; an absolute check would encode one backend's readback convention -// instead of the shading language rule under test. +// Compare channels to test shader coordinates independently of backend readback row order. TEST(ShaderCompilation, FragCoordYMatchesInterpolatedUV) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) @@ -710,13 +719,15 @@ TEST(ShaderCompilation, FragCoordYMatchesInterpolatedUV) << ", row " << (HEIGHT - 1) << " fragCoord=" << last.first << " uv=" << last.second << std::endl; - // Guard against a vacuous pass: the reference ramp must actually sweep the range. + // Guard against the whole comparison passing vacuously: the reference ramp + // has to actually sweep the range rather than sitting at a constant. ASSERT_GT(std::abs(first.second - last.second), 200) << "vUV.y reference ramp did not vary across the target"; - // Both channels come from the same fragment invocation, so they must agree row by - // row whichever end of the image the readback starts at. The tolerance absorbs - // interpolation and 8-bit quantization only. + // Both channels are produced by the same fragment invocation, so they must + // agree row by row no matter which end of the image the readback starts at. + // The tolerance absorbs interpolation and 8-bit quantization only; a flipped + // gl_FragCoord.y misses by the full range of the ramp. for (uint32_t row = 0; row < HEIGHT; ++row) { const auto values = texel(row); @@ -727,8 +738,7 @@ TEST(ShaderCompilation, FragCoordYMatchesInterpolatedUV) #endif } -// `return gl_FragCoord;` parents the symbol on TIntermBranch, which MakeReplacements -// must handle; without that the compiler throws "Cannot replace symbol". +// Direct returns parent the symbol on TIntermBranch rather than an expression node. TEST(ShaderCompilation, FragCoordDirectReturnMatchesInterpolatedUV) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) @@ -776,11 +786,7 @@ TEST(ShaderCompilation, FragCoordDirectReturnMatchesInterpolatedUV) #endif } -// Indexing a screen-sized texture with gl_FragCoord must give the same image as -// indexing it with the interpolated UVs of a full-screen quad; this only holds if the -// gl_FragCoord correction and FlipSamplerCoordinatesTraverser compose to a no-op. -// The two addressing modes are compared against each other rather than against the -// source pixels so the test does not depend on createRawTexture's memory layout. +// Compare fragment-coordinate and UV addressing without depending on upload row order. TEST(ShaderCompilation, FragCoordAndUVAddressATextureIdentically) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) @@ -827,7 +833,8 @@ TEST(ShaderCompilation, FragCoordAndUVAddressATextureIdentically) return static_cast(pixels[static_cast(row) * WIDTH * 4]); }; - // Guard against a vacuous pass: the source must actually vary down the image. + // Guard against a vacuous pass: the source must actually vary down the image, + // otherwise a vertical mirror would be undetectable. ASSERT_GT(std::abs(red(uvPixels, 0) - red(uvPixels, HEIGHT - 1)), 200) << "the source texture must vary from top to bottom for this test to mean anything"; diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index 21018c704..0323ebbfd 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -215,6 +215,13 @@ namespace Babylon::Graphics { Dispose(); + // bgfx creates depth-1 volumes as Texture2D, which cannot bind to sampler3D. + // Pad to two slices to obtain a 3D view, including for placeholder volumes. + if (depth < 2) + { + depth = 2; + } + m_handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); if (!bgfx::isValid(m_handle)) { diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index cf336e79f..52a78d8ca 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -741,6 +741,7 @@ namespace Babylon StaticValue("CAPS_LIMITS_MAX_TEXTURE_SIZE", Napi::Number::From(env, limits.maxTextureSize)), StaticValue("CAPS_LIMITS_MAX_TEXTURE_LAYERS", Napi::Number::From(env, limits.maxTextureLayers)), + StaticValue("CAPS_ORIGIN_BOTTOM_LEFT", Napi::Boolean::From(env, bgfx::getCaps()->originBottomLeft)), StaticValue("TEXTURE_NEAREST_NEAREST", Napi::Number::From(env, TextureSampling::NEAREST_NEAREST)), StaticValue("TEXTURE_LINEAR_LINEAR", Napi::Number::From(env, TextureSampling::LINEAR_LINEAR)), @@ -1893,10 +1894,10 @@ namespace Babylon const auto bytes{static_cast(data.ArrayBuffer().Data()) + data.ByteOffset()}; - // Match the vertical orientation the base upload applies (PrepareImage flips the whole image when - // originBottomLeft ? invertY : !invertY). To land a sub-rectangle at the same place, flip it to the - // mirrored Y origin and reverse its rows so row 0 of the source lines up with the flipped base data. - const bool flip{bgfx::getCaps()->originBottomLeft ? invertY : !invertY}; + // Cube uploads honor invertY directly; only 2D uploads compensate for the backend origin. + const bool flip{texture->IsCube() + ? invertY + : (bgfx::getCaps()->originBottomLeft ? invertY : !invertY)}; const uint16_t targetY{flip ? static_cast(mipHeight - y - height) : y}; const bgfx::Memory* mem{bgfx::alloc(requiredSize)}; if (flip) @@ -2084,6 +2085,16 @@ namespace Babylon // This is required since BGFX must manage the memory backing the update. const bgfx::Memory* dataCopy = bgfx::copy(dataPtr, static_cast(dataSize)); + if (!bgfx::getCaps()->originBottomLeft) + { + // Match render-to-volume storage and the shader compiler's 3D sampler Y flip. + // Flip each XY slice independently without modifying the caller's typed array. + const size_t sliceSize = dataSize / depth; + for (uint16_t slice = 0; slice < depth; ++slice) + { + FlipImage({dataCopy->data + sliceSize * slice, sliceSize}, height); + } + } texture->Update3D(0, 0, 0, 0, width, height, depth, dataCopy); } #endif @@ -3096,7 +3107,8 @@ namespace Babylon const float y{data.ReadFloat32()}; const float width{data.ReadFloat32()}; const float height{data.ReadFloat32()}; - const float yOrigin = bgfx::getCaps()->originBottomLeft ? y : (1.f - y - height); + // bgfx view rectangles use a top-left origin on every renderer. + const float yOrigin = 1.f - y - height; GetBoundFrameBuffer().SetViewPort(x, yOrigin, width, height); } diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp index 81b8471b8..6699a1c74 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp @@ -104,7 +104,7 @@ namespace Babylon::Plugins ShaderCompilerTraversers::IdGenerator ids{}; // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). ShaderCompilerTraversers::FlipSamplerCoordinates(program); - // Must precede the uniform struct move, which collects the uniform this declares. + // Run before uniform collection so the target-size uniform is included. ShaderCompilerTraversers::FlipFragCoordY(program, ids); auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); auto utstScope = ShaderCompilerTraversers::MoveNonSamplerUniformsIntoStruct(program, ids); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp index 563a76800..fe0e9b602 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp @@ -2144,7 +2144,7 @@ namespace Babylon::ShaderCompilerTraversers public: static void Traverse(TProgram& program) { - for (auto stage : {EShLangVertex, EShLangFragment}) + for (auto stage : {EShLangVertex, EShLangFragment, EShLangCompute}) { auto* intermediate{program.getIntermediate(stage)}; if (intermediate != nullptr) @@ -2163,27 +2163,29 @@ namespace Babylon::ShaderCompilerTraversers auto& sequence = node->getSequence(); if (sequence.size() >= 2) { - // The coordinate is the operand right after the sampler. Only 2-component - // float coordinates (sampler2D-style) are flipped; cube/array/3D coordinates - // (vec3+) are left untouched, matching the original flip(vec2)/flip(vec3). + // Flip only V; preserve shadow depth, array layer, and volume depth. Leave cube directions unchanged. + auto* sampler = sequence[0]->getAsTyped(); auto* coordinate = sequence[1]->getAsTyped(); - if (coordinate != nullptr && + if (sampler != nullptr && coordinate != nullptr && + sampler->getType().getBasicType() == EbtSampler && coordinate->getType().getBasicType() == EbtFloat && - !coordinate->getType().isArray() && - coordinate->getType().getVectorSize() == 2) + !coordinate->getType().isArray()) { - sequence[1] = FlipVerticalCoordinate(coordinate); + const TSampler& samp = sampler->getType().getSampler(); + const int vecSize = coordinate->getType().getVectorSize(); + // Esd2D covers both sampler2D and sampler2DArray (arrayed flag separate). + if ((samp.is2D() || samp.dim == Esd3D) && vecSize >= 2 && vecSize <= 4) + { + sequence[1] = (vecSize == 2) + ? FlipVerticalCoordinate(coordinate) + : FlipVerticalCoordinateKeepTail(coordinate); + } } } } else if (visit == EvPostVisit && node->getOp() == EOpTextureFetch) { - // texelFetch(sampler, ivec coord, lod). The vertical flip that used to be applied - // by a preprocessor macro in ProcessSamplerFlip is done here instead so that the - // sampler dimensionality is known: only 2-component integer coordinates - // (sampler2D-style) are flipped. sampler3D / sampler2DArray coordinates (ivec3) - // are left untouched — the old macro forced every coordinate through ivec2(...), - // which failed to compile against sampler3D ('no matching overloaded function'). + // Flip integer Y using the selected mip height, preserving volume depth. auto& sequence = node->getSequence(); if (sequence.size() >= 3) { @@ -2191,11 +2193,15 @@ namespace Babylon::ShaderCompilerTraversers auto* coordinate = sequence[1]->getAsTyped(); auto* lod = sequence[2]->getAsTyped(); if (sampler != nullptr && coordinate != nullptr && lod != nullptr && + sampler->getType().getBasicType() == EbtSampler && coordinate->getType().getBasicType() == EbtInt && - !coordinate->getType().isArray() && - coordinate->getType().getVectorSize() == 2) + !coordinate->getType().isArray()) { - sequence[1] = FlipVerticalTexelCoordinate(coordinate, sampler, lod); + const int vecSize = coordinate->getType().getVectorSize(); + if (vecSize == 2 || (vecSize == 3 && sampler->getType().getSampler().dim == Esd3D)) + { + sequence[1] = FlipVerticalTexelCoordinate(coordinate, sampler, lod); + } } } } @@ -2235,39 +2241,69 @@ namespace Babylon::ShaderCompilerTraversers return m_intermediate->addBinaryMath(EOpAdd, scaled, offset, loc); } - // Builds `ivec2(coordinate.x, textureSize(sampler, lod).y - 1 - coordinate.y)`, the - // integer-texel-coordinate equivalent of FlipVerticalCoordinate. This is exactly the - // expression the former ProcessSamplerFlip texelFetch macro expanded to, including its - // double evaluation of the coordinate operand. - // - // The obvious vector form `coordinate * ivec2(1, -1) + ivec2(0, size.y - 1)` must NOT - // be used: it emits SPIR-V OpIMul, which SPIRV-Cross omits entirely when built with - // SPIRV_CROSS_WEBMIN (the configuration Babylon Native ships). The multiply then - // silently produces no HLSL/MSL expression and the whole shader fails to cross-compile - // with "Cannot resolve expression type". Integer subtract and vector construction are - // both retained by that build, so express the flip with those only. + // Preserve trailing components. Scalar extracts avoid OpVectorTimesScalar in SPIRV_CROSS_WEBMIN. + TIntermTyped* FlipVerticalCoordinateKeepTail(TIntermTyped* coordinate) + { + const TSourceLoc& loc{coordinate->getLoc()}; + const int vecSize = static_cast(coordinate->getType().getVectorSize()); + TType floatType{EbtFloat, EvqTemporary, 1}; + TType resultType{EbtFloat, EvqTemporary, vecSize}; + + // coordinate is read once per component; clone so no node ends up with two parents. + TIntermTyped* coordXSrc = coordinate; + TIntermTyped* coordYSrc = CloneExpression(coordinate); + TIntermTyped* coordZSrc = (vecSize >= 3) ? CloneExpression(coordinate) : nullptr; + TIntermTyped* coordWSrc = (vecSize >= 4) ? CloneExpression(coordinate) : nullptr; + + TIntermTyped* x{m_intermediate->addIndex(EOpIndexDirect, coordXSrc, m_intermediate->addConstantUnion(0, loc), loc)}; + x->setType(floatType); + TIntermTyped* y{m_intermediate->addIndex(EOpIndexDirect, coordYSrc, m_intermediate->addConstantUnion(1, loc), loc)}; + y->setType(floatType); + + TConstUnionArray oneValues{1}; + oneValues[0].setDConst(1.0); + TIntermTyped* one{m_intermediate->addConstantUnion(oneValues, TType{EbtFloat, EvqConst, 1}, loc)}; + TIntermTyped* flippedY{m_intermediate->addBinaryMath(EOpSub, one, y, loc)}; + + TIntermAggregate* ctor{m_intermediate->makeAggregate(x, loc)}; + ctor = m_intermediate->growAggregate(ctor, flippedY, loc); + if (vecSize >= 3) + { + TIntermTyped* z{m_intermediate->addIndex(EOpIndexDirect, coordZSrc, m_intermediate->addConstantUnion(2, loc), loc)}; + z->setType(floatType); + ctor = m_intermediate->growAggregate(ctor, z, loc); + } + if (vecSize >= 4) + { + TIntermTyped* w{m_intermediate->addIndex(EOpIndexDirect, coordWSrc, m_intermediate->addConstantUnion(3, loc), loc)}; + w->setType(floatType); + ctor = m_intermediate->growAggregate(ctor, w, loc); + } + + const TOperator op = (vecSize == 3) ? EOpConstructVec3 : EOpConstructVec4; + return m_intermediate->setAggregateOperator(ctor, op, resultType, loc); + } + + // Build (x, textureSize(sampler, lod).y - 1 - y[, z]). + // Use subtraction and construction: SPIRV_CROSS_WEBMIN omits integer vector multiplication. TIntermTyped* FlipVerticalTexelCoordinate(TIntermTyped* coordinate, TIntermTyped* sampler, TIntermTyped* lod) { const TSourceLoc& loc{coordinate->getLoc()}; - // Every operand referenced more than once below has to be an independent subtree. - // Reusing a node pointer would give it two parents in the AST, which later traversers - // (sampler splitting, SPIR-V generation) do not expect. The sampler and lod are each - // referenced twice because textureSize repeats them, and the coordinate is referenced - // twice because the flip reads both .x and .y, so all three need a copy. The original - // node is kept for one reference and the clone used for the other, leaving every node - // with exactly one parent. + // Clone repeated operands so each AST node retains one parent. TIntermTyped* samplerClone{CloneExpression(sampler)}; TIntermTyped* lodClone{CloneExpression(lod)}; TIntermTyped* coordinateClone{CloneExpression(coordinate)}; + const int vecSize = coordinate->getType().getVectorSize(); + TIntermTyped* coordinateZClone = vecSize == 3 ? CloneExpression(coordinate) : nullptr; - TType ivec2Type{EbtInt, EvqTemporary, 2}; + TType vectorType{EbtInt, EvqTemporary, vecSize}; TType intType{EbtInt, EvqTemporary, 1}; - // textureSize(sampler, lod) -> ivec2 + // textureSize(sampler, lod) has the same dimensions as the coordinate. TIntermAggregate* sizeArgs{m_intermediate->makeAggregate(samplerClone, loc)}; sizeArgs = m_intermediate->growAggregate(sizeArgs, lodClone, loc); - TIntermTyped* size{m_intermediate->addBuiltInFunctionCall(loc, EOpTextureQuerySize, false, sizeArgs, ivec2Type)}; + TIntermTyped* size{m_intermediate->addBuiltInFunctionCall(loc, EOpTextureQuerySize, false, sizeArgs, vectorType)}; // textureSize(sampler, lod).y - 1 TIntermTyped* sizeY{m_intermediate->addIndex(EOpIndexDirect, size, m_intermediate->addConstantUnion(1, loc), loc)}; @@ -2281,11 +2317,17 @@ namespace Babylon::ShaderCompilerTraversers TIntermTyped* coordinateY{m_intermediate->addIndex(EOpIndexDirect, coordinateClone, m_intermediate->addConstantUnion(1, loc), loc)}; coordinateY->setType(intType); - // ivec2(coordinate.x, (textureSize(sampler, lod).y - 1) - coordinate.y) + // Construct (x, flipped Y), preserving Z for a volume fetch. TIntermTyped* flippedY{m_intermediate->addBinaryMath(EOpSub, maxY, coordinateY, loc)}; TIntermAggregate* flipped{m_intermediate->makeAggregate(coordinateX, loc)}; flipped = m_intermediate->growAggregate(flipped, flippedY, loc); - return m_intermediate->setAggregateOperator(flipped, EOpConstructIVec2, ivec2Type, loc); + if (coordinateZClone != nullptr) + { + TIntermTyped* coordinateZ{m_intermediate->addIndex(EOpIndexDirect, coordinateZClone, m_intermediate->addConstantUnion(2, loc), loc)}; + coordinateZ->setType(intType); + flipped = m_intermediate->growAggregate(flipped, coordinateZ, loc); + } + return m_intermediate->setAggregateOperator(flipped, vecSize == 3 ? EOpConstructIVec3 : EOpConstructIVec2, vectorType, loc); } // Produces an independent copy of an expression subtree so it can be referenced from a @@ -2374,17 +2416,9 @@ namespace Babylon::ShaderCompilerTraversers TIntermediate* m_intermediate{}; }; - /// Presents gl_FragCoord in OpenGL's coordinate space on the top-left-origin backends - /// (D3D, Metal, Vulkan). FlipSamplerCoordinates already flips every sample coordinate, so - /// gl_FragCoord was the one input left in physical space -- making - /// `texelFetch(tex, ivec2(gl_FragCoord.xy), 0)` read the mirrored row. - /// - /// The flip is `targetHeight - gl_FragCoord.y`, with no -1 term: the hardware yields - /// p + 0.5 for physical row p, and p == height - 1 - y, so the GL value y + 0.5 is exactly - /// height minus the incoming value. - /// - /// The height cannot come from bgfx's u_viewRect, which SetBgfxViewPortAndScissor narrows - /// to the viewport, while gl_FragCoord is relative to the whole render target. + /// Convert gl_FragCoord to GL coordinates on top-left-origin backends. + /// Pixel centers require targetHeight - y, without a -1 term. + /// Use the full target height, not u_viewRect, which may describe a smaller viewport. class FragCoordYFlipTraverser final : private TIntermTraverser { public: @@ -2404,8 +2438,7 @@ namespace Babylon::ShaderCompilerTraversers return; } - // Declared as a linker object so MoveNonSamplerUniformsIntoStruct sweeps it into - // the "Frame" struct with every other non-sampler uniform. + // Declare before uniform collection so target size joins the Frame struct and uniform table. TType targetSizeType{EbtFloat, EvqUniform, 4}; TIntermSymbol* targetSize{intermediate->addSymbol(TIntermSymbol{ids.Next(), Graphics::FRAGCOORD_TARGET_SIZE_UNIFORM_NAME, targetSizeType})}; @@ -2422,7 +2455,7 @@ namespace Babylon::ShaderCompilerTraversers protected: void visitSymbol(TIntermSymbol* symbol) override { - // Linker object references declare gl_FragCoord rather than read it. + // Linker references are declarations, not reads. if (symbol->getName() != "gl_FragCoord" || IsLinkerObject(path)) { return; @@ -2461,23 +2494,19 @@ namespace Babylon::ShaderCompilerTraversers { for (const auto& [symbol, parent] : m_symbolsToParents) { - // Not batched into one MakeReplacements call: that maps one replacement per - // symbol *name*, so every gl_FragCoord reference would share one subtree and - // that node would end up with multiple parents. + // Build a fresh replacement per occurrence to avoid sharing AST parents. MakeReplacements({{"gl_FragCoord", BuildFlippedFragCoord(symbol, targetSize)}}, {{symbol, parent}}); } } - /// Builds `vec4(gl_FragCoord.x, targetSize.y - gl_FragCoord.y, .z, .w)`. The whole - /// vector is rebuilt rather than patching .y because a reference may be swizzled, - /// indexed, or passed along whole, and the parent node is not inspected here. + /// Reconstruct the vec4 to support whole-vector, swizzled, and indexed reads. TIntermTyped* BuildFlippedFragCoord(TIntermSymbol* fragCoord, TIntermSymbol* targetSize) { const TSourceLoc& loc{fragCoord->getLoc()}; TType floatType{EbtFloat, EvqTemporary, 1}; TType vec4Type{EbtFloat, EvqTemporary, 4}; - // Each component gets its own symbol copy so no node ends up with two parents. + // Each component needs its own symbol node. auto component = [&](int index) { TIntermTyped* copy{m_intermediate->addSymbol(*fragCoord)}; TIntermTyped* element{m_intermediate->addIndex(EOpIndexDirect, copy, m_intermediate->addConstantUnion(index, loc), loc)}; diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h index ba25d36a3..bab811bda 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h @@ -180,9 +180,7 @@ namespace Babylon::ShaderCompilerTraversers /// OpenGL backend shares bgfx's V-orientation and must not flip. void FlipSamplerCoordinates(glslang::TProgram& program); - /// Rewrite every read of gl_FragCoord to present it in OpenGL's bottom-left-origin space. - /// Must run before MoveNonSamplerUniformsIntoStruct so the target-size uniform it declares is - /// collected with the others, and only on the backends that apply FlipSamplerCoordinates - /// (D3D, Metal, Vulkan); OpenGL already matches WebGL's origin. + /// Convert fragment coordinates with bnFragCoordTargetSize on D3D/Metal/Vulkan. + /// Run before uniform collection; shaders without gl_FragCoord remain unchanged. void FlipFragCoordY(glslang::TProgram& program, IdGenerator& ids); } diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 48c54be73..0800eebdc 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -52,6 +52,26 @@ namespace Babylon::Polyfills::Internal namespace { + class ScopedPath + { + public: + explicit ScopedPath(NVGcontext* context) : m_context{context} + { + nvgSavePath(m_context, m_path); + nvgBeginPath(m_context); + } + ~ScopedPath() + { + nvgRestorePath(m_context, m_path); + } + ScopedPath(const ScopedPath&) = delete; + ScopedPath& operator=(const ScopedPath&) = delete; + + private: + NVGcontext* m_context; + NVGsavedPath m_path; + }; + // True only for a finite, non-negative integer that fits in a uint32_t. Used where a // dimension arrives from a duck-typed object and so has not been through WebIDL's // unsigned long conversion; Uint32Value() would silently wrap -1 into 4294967295. @@ -1239,11 +1259,10 @@ namespace Babylon::Polyfills::Internal nvgGlobalCompositeOperation(*m_nvg, NVG_COPY); NVGpaint imagePaint = nvgImagePattern(*m_nvg, destX, destY, destWidth, destHeight, 0.f, imageIndex, 1.f); - ResetPathState(); + ScopedPath temporaryPath{*m_nvg}; nvgRect(*m_nvg, destX, destY, destWidth, destHeight); nvgFillPaint(*m_nvg, imagePaint); nvgFill(*m_nvg); - nvgRestore(*m_nvg); } @@ -1600,28 +1619,11 @@ namespace Babylon::Polyfills::Internal imageIndex, 1.f); - if (m_isClipped) - { - // The current path is the emulated non-rectangular clip. Restrict it to - // the destination rectangle with a temporary scissor instead of appending - // the rectangle to the path, which would fill and retain their union. - nvgSave(*m_nvg); - nvgIntersectScissor(*m_nvg, rectangles.X, rectangles.Y, rectangles.Width, rectangles.Height); - nvgFillPaint(*m_nvg, imagePaint); - SetFilterStack(); - nvgFill(*m_nvg); - nvgRestore(*m_nvg); - } - else - { - // Rectangular clips live in NanoVG's scissor state and survive resetting - // the temporary draw path. Keep the wrapper's path flags in sync as well. - ResetPathState(); - nvgRect(*m_nvg, rectangles.X, rectangles.Y, rectangles.Width, rectangles.Height); - nvgFillPaint(*m_nvg, imagePaint); - SetFilterStack(); - nvgFill(*m_nvg); - } + ScopedPath temporaryPath{*m_nvg}; + nvgRect(*m_nvg, rectangles.X, rectangles.Y, rectangles.Width, rectangles.Height); + nvgFillPaint(*m_nvg, imagePaint); + SetFilterStack(); + nvgFill(*m_nvg); } void Context::RetainImageUntilFlush(int imageIndex) diff --git a/Polyfills/Canvas/Source/nanovg/nanovg.cpp b/Polyfills/Canvas/Source/nanovg/nanovg.cpp index 0ad1fb662..7339fcfe2 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg.cpp +++ b/Polyfills/Canvas/Source/nanovg/nanovg.cpp @@ -1961,6 +1961,23 @@ void nvgBeginPath(NVGcontext* ctx) nvg__clearPathCache(ctx); } +void nvgSavePath(NVGcontext* ctx, NVGsavedPath& path) +{ + path.commands.assign(ctx->commands, ctx->commands + ctx->ncommands); + path.x = ctx->commandx; + path.y = ctx->commandy; +} + +void nvgRestorePath(NVGcontext* ctx, const NVGsavedPath& path) +{ + nvgBeginPath(ctx); + if (!path.commands.empty()) + memcpy(ctx->commands, path.commands.data(), path.commands.size() * sizeof(float)); + ctx->ncommands = int(path.commands.size()); + ctx->commandx = path.x; + ctx->commandy = path.y; +} + void nvgMoveTo(NVGcontext* ctx, float x, float y) { float vals[] = { NVG_MOVETO, x, y }; diff --git a/Polyfills/Canvas/Source/nanovg/nanovg.h b/Polyfills/Canvas/Source/nanovg/nanovg.h index 2a0938c55..4478d8aa8 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg.h +++ b/Polyfills/Canvas/Source/nanovg/nanovg.h @@ -19,6 +19,8 @@ #ifndef NANOVG_H #define NANOVG_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -438,6 +440,13 @@ void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); // Reset and disables scissoring. void nvgResetScissor(NVGcontext* ctx); +struct NVGsavedPath { + std::vector commands; + float x, y; +}; +void nvgSavePath(NVGcontext* ctx, NVGsavedPath& path); +void nvgRestorePath(NVGcontext* ctx, const NVGsavedPath& path); + // // Paths // From e814291f77b138aa54cca8a0aae494f18df970ca Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:16 -0700 Subject: [PATCH 08/16] Align Canvas glyph rasterization and metrics Adjust fontstash glyph placement and script-aware GPOS kerning to better match browser text rendering. Report an integral CSS line box separately from precise ink bounds so GUI layout and measureText agree. Add text regressions and font-loading hooks, and package licensed Arimo and DroidSans assets with their provenance. Review-Group: N5 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 2ef0aedee35ae5a7cd2aa6c6265161c45c35ab6a Split-Part: code --- Apps/Dependencies/Arimo-OFL.txt | 93 ++++++++ Apps/Dependencies/Arimo-PROVENANCE.txt | 11 + Apps/Dependencies/Arimo-Regular.ttf | Bin 0 -> 478712 bytes Apps/Dependencies/DroidSans-APACHE-2.0.txt | 202 +++++++++++++++++ Apps/Dependencies/DroidSans-PROVENANCE.txt | 13 ++ Apps/Dependencies/DroidSans.ttf | Bin 0 -> 190044 bytes Apps/UnitTests/CMakeLists.txt | 9 +- .../JavaScript/src/tests.javaScript.all.ts | 45 ++++ Apps/UnitTests/Source/Tests.JavaScript.cpp | 8 + Polyfills/Canvas/CMakeLists.txt | 1 + Polyfills/Canvas/Readme.md | 5 + Polyfills/Canvas/Source/Context.cpp | 3 +- Polyfills/Canvas/Source/MeasureText.cpp | 10 +- Polyfills/Canvas/Source/StbTrueType.cpp | 208 ++++++++++++++++++ Polyfills/Canvas/Source/nanovg/fontstash.h | 38 ++-- 15 files changed, 624 insertions(+), 22 deletions(-) create mode 100644 Apps/Dependencies/Arimo-OFL.txt create mode 100644 Apps/Dependencies/Arimo-PROVENANCE.txt create mode 100644 Apps/Dependencies/Arimo-Regular.ttf create mode 100644 Apps/Dependencies/DroidSans-APACHE-2.0.txt create mode 100644 Apps/Dependencies/DroidSans-PROVENANCE.txt create mode 100644 Apps/Dependencies/DroidSans.ttf create mode 100644 Polyfills/Canvas/Source/StbTrueType.cpp diff --git a/Apps/Dependencies/Arimo-OFL.txt b/Apps/Dependencies/Arimo-OFL.txt new file mode 100644 index 000000000..756c77530 --- /dev/null +++ b/Apps/Dependencies/Arimo-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2026 The Arimo Project Authors (https://github.com/googlefonts/Arimo) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/Apps/Dependencies/Arimo-PROVENANCE.txt b/Apps/Dependencies/Arimo-PROVENANCE.txt new file mode 100644 index 000000000..2e36d5a59 --- /dev/null +++ b/Apps/Dependencies/Arimo-PROVENANCE.txt @@ -0,0 +1,11 @@ +Arimo-Regular.ttf + +Source repository: https://github.com/googlefonts/Arimo +Source commit: 4a6255f269916ae7ad3fc2706b0935e7621396b8 +Source file: fonts/ttf/Arimo-Regular.ttf +Immutable URL: https://raw.githubusercontent.com/googlefonts/Arimo/4a6255f269916ae7ad3fc2706b0935e7621396b8/fonts/ttf/Arimo-Regular.ttf +Git blob: d6b78c111a336f8c42e4cf5b6546c7b3386146cd +SHA-256: 41B22BC8F0B51F932825D37BC55B5EB6BA67DFE599A626E4AFF2B43B624F9F8C + +Copyright 2026 The Arimo Project Authors (https://github.com/googlefonts/Arimo) +Licensed under the SIL Open Font License, Version 1.1. See Arimo-OFL.txt. diff --git a/Apps/Dependencies/Arimo-Regular.ttf b/Apps/Dependencies/Arimo-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d6b78c111a336f8c42e4cf5b6546c7b3386146cd GIT binary patch literal 478712 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

<@Rz0fyL+xP1ET{}Us2EM2;zYZo55bLX;U zJIcQ=KV;AY%t2=hSI$!|LIC*yajmn`1ZrK|SI z_FYSsLIUMc+1h@&>6GEmO*Ctpc}KijFPNb5}G9elDwduNbKKk$J~XJ*S8TLW1(!+I@aixP$#nv-(UWxvoU*tErrmx~mg$z($rVpdlV7PPdWB4E8pJpr#RL%$Ya8&#K?x5J!kz_zo!Bhf&jHHVQCv5n%x9v zny1uw&7xJ@U)y|I3gbE2O>QmWLrQp*_DP8b2XC=22o^H#0Blr6=S}U%)Pr0(GMpjZ zfGAWq<3M*YVJrsD6XbSD(xy;bm=PeS<84ygmli%cYYTxy_@?>V5TP;h7uFn%EnfU<(;$Ct-`p@?9XD;n|m@z;)H|Zc8$>d-9IU0^XES$+c(T*M&vE-LCy!{t&KN`05 z-YMbRm)Omw@~p?Q4+9MhiMbf!V606hPSy zDKD3F?m`2qg3q)e47O%eO{kBfZ8mpXDB@q#)8{`n5-454>?kYZn^8_xc(P;EAdIZW8#;*rRaEfDy z(t{-AoboU}b2Xp;CZE4X+uY>Wcjpb?^Y0BDB9JrSBNTo~3VHYzGE zhFn_(KWc;(8Wc87Tnn=6I2|<7a94ccVuh#(!G+<5_{uLw*z2y)?rqv*Z~E4hE;A#b zo|{&bGO$vWnvu5>?cNP5leQ)jN<+APTH*9&fOIX9O^KZx5 zmNn77bv(=&-8^CW!JN^vR&ZF*I9mgqOC6lKC*2aTTik=Z7eHI!;*Fp2O&W-?fEp0D zt7`-wN9+0_4+V_sfGmX8;Dk9>>pUDJO>`_*nwKAh%hV3->s3SLI GRSu>6PjvVL z#@mDOj4Z}(!x_pX2SXE@4Kyu$jJVtZo9uLMqTUIXQ24IX%B#nen|}^BCjIfIf#7w$ zmL-*_UjpX>PgTLjdn2$A|Di)tIAZED9feCA0QXL^0y9+=>MwupuNo>et0}Tzb;zVa zTE?i3sg3IIk@jxOsZqE2p1oR@d0F6h%85Nkd`m*Ud)R!>QG$bDX+C17S1~KA!8+Nq z!h_&A9}G+{Oa&O6PsM<4Dt9Y@T&x<%nu)k1Cl5bB%Cbe`y=F0yHEcD)kTb&9$GZw# z2o+}KR!=Wo0h%0L;k5%Oi4b~E9e&~6Vu%ImOc8E7Tp2Qj*E`s{Q;SaS%Y8N!@wiZ% zDY%ZUZ6`%(ThhI7-V0r%`OgphNmUwkYS%EsW6-z<+LZ>0ZuwO*nk^gtLWgFGp?S0M zw5AZO={nX#HbO*%<9zTRO`7N7gfMA}eN?=_ND0dfF^CbB4TRG5{rh$tIJlelSl%Ol zNY5SvhWAu_Ph7rq>f|MD!kX!`S1c(OcIP3u5TwJ1Va@DKg5WeIL)gZ(Cf@{tz5)sn z$Eex#sassQ)PXbbbD<%@{$`3)XE8j+4M)h~TkY>B8L>r4fW=f_{UZ7MsQ8QgY7lM& zGAjGN!ENh4csn^i`M%@Op;zvF`0*V+%yH;GDz1IFjw6hErS|ptOWK~%s-g5&A0Bq> zTTS~`TXgPLshd2KcYWhL4_=AwX#VI&!&%tdqFDm;28Zlbg8cn(XQCAe%19_bJitmt zdAUoHCpQ=+!$4qAS%LqLqGV-)H8BdJDI~Y{`(w~Ho_QK1&un^v{f`I0qDi9lKril& z#_s>nJ5p3wVq)s0)~y|r5R(uc=^GgZtZ1!5sX)M*I7ze5V-SO5SOAU_ltLJd(#$T` z7($vrPk=Q*+g7;9Xs4g+@XpfhJNd1C2MaFT#o~1xs z7R;HdGiL@>XU-K4smu6|P3+woS;H9>2pYg?3Vpns4Gi2~yE6(%WD;k1GONW-Jb z>T_tx!QH}8NJhD8p~3Ka^7O!uGPDx9X9$PblLogOZ>fT)=&^WF(*1K<=l)|m74v;k z^Ji1*=QVQ1C2HmG--JTH!KF(N{}{O`n*tLjQ($62MPMRkb5A?YkBRviDGLCJ?AXa? z#f%hvM*qbjMusVmA3Lic#So1FU*P-YV8d_*B4>nj57?6YUr{j&Cq@zo9US?vIbDTwn2>go2k{;bt`Wd=FBI-8O(*|HAuUCgnQrPdktOwTrUTXab&4bmGegrVCQNA|E~y^Wp7^%_R?^G z9Kk1O5f~Ek%rxdfS4jRrG>kZ*3J1q_6hRdhLb;2Czb3$$Meqpn*yOqw81#x@QYt)p zpjC9aiSavd3wv+exXeW1+_P$p@D)=VlXKX^bF1e$y?i2_K4Unidc32OAm0bAtjT)Y zd-#&2$V`@^>fyjzHL8KLS6uO4P$gjQaF8xwMubx5f}w>70}X@GsN})N(WrC^uy7V1 zOU{6nngKN*Q&bN^$Kb0#Mk|a-PD@hdT1!AR$ypr-1JledO-HpEwfwo6!$#MfKC;1> zuBe;cSs0y?Ck^%QlvAx}bn}tY?=^yJMkg)qQadi9Hw;c%_k`fu@wJP)gxL(i{ymq~ zwqc#%xRlS;B79b*_-b&9_&@aR5}R4+z`-^O7{Vn16!|J(!;@0Ls`^uGCb#lh5_~*9 z;+kWbCDx+7B)8Z0`b7s8EB7CI6)V4V7?V=IMedzAq!YoJ1Y2sc5+UyaoSA4$lTQ^d zSTb=#J%I5bH~|hgU>BcGG2M)0Rpi}(Bp_hU;qE89z9A7!zLEUjRikVmr|v!qNEpf3Zf2GpwR!YEZAl6By5Oe?~6i(Q7WPAIj(wP@6+p7 z^WLIf*!x6%qG-RGzbf`8@6qqZZR!HZvQg}2`~3+ZwH|?7=260(19XAhu&Kt)S>yV{ z9b`Ngt}^sQB~Y0C;g{EGdtP6ggiQ zq=+>68~f4^v`gEz@wAUp1}7sH<5TGyN06NH@VA=jReyWLjX%V<2=>^^Ti*PPH{NAX zok|YnM@9Pr@t60t_vw6zF2kl~Oau=UEV!Tyds~0*Ztye$X%PDhRjtb0)q$Q+#~EB+ zsKnaon*sOS*cisD$HyeZCPYPshoPI`$SC~Zi==Ly`+$>fM6AK;bkq$AVe_m+|Ub7W2>H%PXZw{>|4Ei9j%dOdrlce%shF^;o#{S)A^I_=dU?{ zlu-{CN*QzEBw@nU|r8jti5~;PQ!ZKGV%c$RXAybCq}XaqNuHSmIo}d z9%c`-$p;x*;F;kEK^MAxTq;m`c;1q(PyZ(6o{g$Lq4%%nqHB!rqf9)vw9oPubEceI znzyXQ+{uKyDv#AfXI=d;78W^@2GgCFF=U9{Ox0}G6Sd%qOul}|r=)AGT=|rc-prX< zN$L|>bA0cdv)W;2!ic-QmbS$NmJ}?q<3r!8Lird^7wd(@0UA&9>W?X4STqpp6D~#- ztK!Wo)}t?kv&Vfrl?#g203t7z;6o=vc2`$bMJ^~vGp<}v>8_d56enH42{vOlq1PFM zZ-nn4uYiyf9%RT!2oswsOF#~!IfaT;&kUg|9sU6W(m@HK8K2Uu9a3v(V_NM{#vDDW zOU%gVYC{p!la2$i$yxrf1LO6YB%<3>5X>BjU#?+V_cD)AKG^#?6~*% zK?_&<^HXu!voIs@^Kse`%jPNldw8$iA$b(}#hNYEErq;%=Nj)G{r8?I3~Yj`qH~7{ znsQjN)Jf3(NPD;-`YPayR3|_dbd7XeVHQ^Y3VJVf`YUzEcH^mB^w(MR2OmkGQ>3rK zX@h}5SMW{%pHGNL@W0?z{U`xhnv0m{>Lk3Awy!ejrha9p#PISB^$MLMzW1^B^x+1e zKjGJCFz`Ip6a5Mgri$-gZZLEzeX4i>><}hE+H{!hgM*2(2ZsfRg@zyvJaTXP`Btrh zlW`>w^>#@dP})>tS;;~Ve@L>m$uE>?+bah+@CL#w-togzaSqdUFL}BPH2<;A`M0&V z)@Ki&*$e;E+&-dZbF{xS)n}iNwvrbF z2P6MzKK|eZ+RETyp8tb3hmQ-}5(1x*b`{0T3=REvSLtAfFZL6lx&md*oo zDpwn5m3ttMsL9PoHh6IlPlLxGs0UbHRoPo+NOM_*dwNQF?nW3EA7>T)Uyrr_>tn=4 zCjH%LRiPJx9-YSY2EO>qqxQ6a^%U@GYcZ-3!kN*SVWg;bd+oT`&=4;~<)*i8Zg1Q$ zEu~gMY+_tuWJE|zXiRV*d;o>L18yLm9RA{@MI*Nl-RP?VqgQfKS1bLb8_M-&m6|v? zGhx>0zhcpjvJi`%dyHdT5HO31Ib~P{r#847ts}uzcX3y7`m_0IEn_&(|%ClITk$->X zR>2MFTjhWs_tx(D?c9mS9({*CkEkc%|5{)4S&M7v-Q{0>OT>RJ}0vm_}R88)6N zr2TyKDb_Jo{Tg_xu2@HmP)|T#0XTsNuOvKoC0YYW3i%M?#*$OGYyL^DDaX$^;yWhn z#4Dj={0}JycFkYCdf|2qUaCg$j)Hdn`&envh>n6NV-`uuVky8d8d&uIpTA*1^Pw%8 z4{NUfHzccG{j7|J4KmbedPU2j_GUv{)XU1Mm)4n-ES!yn&-R!2W@hAB} zg*%0`AQ1?pBxMv%z-5DT8Gh*bP9gRPy@fROzctA{YVu9ASt;I%wpR7C>R$IL$)wF9 z6TF3gsoiNDmKe2crX|$aY;bJv-j2i2F?P!2{78Sb>(}pH$JS67%kcd+AY+B-zHsYB|z#2h+>_i;B0|lSOGpxgijDJlD6@^O*0Ax zWw)G}+N*bJV{Jspb6s1{h^aQQUH3q74h*omLfm7U7%LDGzblV|Qz`-fQ_(%#T2qoJ zC!Dn6)|Kz`zm;wjsHe4i+*@2>%n{lQh4KznA5^k>$-3p5Unw5J7$o++4EXmX zo8!}C=n0y^g`g>-S^SBkcJ-d-Z#2!DFrjSHB&k}<^(_(SkdwZljbj3ErpS*XaC<5q zG)gP^Xn`|DjFa$*?nd?OGxn0GZ-#ZgDB3r6wg;|7KOzo8wD09?Pm`7^#V9f-dps;I|4sHpx9Ha!a^3*3-yy7~y*fB92(vEl zR>}{FJ%8-oYK3PbicNU8>KW3=x78Zq%92KUro_~WOlfP@JeezPg*J#c*zoOS>FO=* z)JxLrHOS!XOqr(DU!(a_)-*jXF;@Lv@Mw^(EyB9z0ec_}XSm;fTxy3?q#(yZfN2OR z#4$lEBGV0L(YEj~lMk>uvdKBa>2)=EOstv$S5<_321FZ;7GM7-wT@^N26wr>YdQR7Ts*dh@Pd|pl==D z^54$9wDgZr?hIK!Gkb|yNh%g)O=Y&WRaWo?Qp=!u5p*B&CyfcOhNkDrCb+M{rP z^el1ET|;d~`5*+~116E34!iGEuE4n%XA27_(lG$A}~fBEIr-3cBQzE21pvo(_=uD(SHM{0|vM_{_kW1sO%9drOyMns|00`&N4-0Ph^F^f}&7+nCRL^WFvUx9t0z)KXhlKx&eK+6LaIK-;2i zBxiw+Qj#D2KrS*dcOGD<3n@gE#U`WB3(;=sXv7U#)6Ny-1-GN>L(U6p> zI3k(OWB?3FfPJVG^wE*g!Px*P7%IYo%0K7YtDqfRVIqCwO$M^q`&(lXI9B0jfpg>UJNX$kU@LjP0VHGsjnIC&VMYQJ-+Q$$O zK2i&j7ps$ebC!qR@hb$u=qM}H*aj~>fp!F3(vuQFm4SLmd6v^MM)uPtDSA+>+ zJt7yR;aA|S>ZmYEYx(b@UgVolzeasKs%IZE>Fr5=(c7=&S1PrqI3d0LXNWj*(VL;bgW>SonQ$Z+$^PJXu5*zwu<1ZCMvmu* zL1I9R?(QO_4~WstO}AvUrVPr}|DmC9!H51g4}>a%LRrA%RF8{_#DL)tXUGp$xSS$- zMFa>3*#445CB>Q|3p0rB$|9$f&6r`>^K>E=?cLv9c~sQ;%a%>A)5~u^ zPNu>i{jBa$CK<>#;NQyvz_pVejy8DohPjRI`>)V79r^dVrXxbvg!2PT!>&iL6@`8X zwsg5#kFz7#YDx9%L%IZ}XX)0(wqCMmatEfjz z2;TZdaEtchOt1XqOobjtzzZgeGyRyKW|s+Kr2Us&ur2Zoh?vtHF`th)kQ2t$bwISw zv{0k8hOZO#Xs=Wg`=+=51RN^0Z-#lkDE7IrXuq6q6!p41L;KK6^k2-euNCd7f3z3y zHt8>PLVxy3#j_tV2l}Agv>Ybt_(%lE|{c!oniLBFvP<)#yg%XbH~v zF5CK3SzKj!82*n;uGbxhrPs;-{xDDf?2Erm{>h))xDoRl^XNDAjyfNf{csk`;Ijlx z7Q6=FTY>j`fiBU$(F;bfa?TTALkC*~F@Qo9JlrTJJIrBZ`h{{9e#Ok1LP{} za^?@K?r`IgBibLo@Zk>2nO9ypBLQjYGTQ*y|3d(gId=ER5I-+)b}lj+0Ova0)Ss+N zYhY_}fk8JZwM#AXy%YP6U-3o)y$MbeEx_#AVgmf}ehs%!q|$(T!j&8?N%vl}_=-Fh zPA38Q;Th!iP4|s|^VN^uo^q(BWJx+DhM_@2*|NK*68N4oLWV6 zmq<$B4D!NOQZQ$deNRGY&Drg<^KhO{IC&eoVjn;XGJRCsUcdlJ z@%o|LVyp;H6nBlb5y3fDcnHNJ(*wfwXjTfOS)aK-*++*+X z>MPgtAAW&ODTo`nJCB}jP{w~Y;nB5Qm$YYJMgMTJ!~Vg?5SCB)iIX`Msfc6--#$pW zaP&eGvUFiaj zz`@B7DX&#ehOIcO+34rwqaG%Y`Cg{!j%PAj!!cee@*6ozdYAU3E9CHxmCn#xTiG_d z2fPJwagaZ*oo<;5#qALDprfR`4Y>IXmxS{=6-ku;woT&WHk3j6Z`)M#^Y=fyx*!XZ zhi{ZE7;zm&H-tywg<`<1den7*0UHn?BAoR%q?qvD8|7oie|Fvb?dApl@?|U6tlxh6 zWOm2Jo0m%|NSgnFHzg+ZL<&8RZ+P{HIceIw_b$0fEnj_C`{cJX?`pp~jvI9w2=aPn zDf1rf1XgLzZUf&n#|fTdrUQ&0dvqH_jM$%3PX6RLkb4^;dnssTgy4Kpoc93!iBzWC z#(B3q9K>;h!Q9i^6-k2EGpyu`z>xxSYq8+K^DC0aRs4{9)KBEn@d~M-$1PE8tnPh8 z`#@SHOTf+Q!@5iyHlcZT_P_}k=6MvQYevh$g_^>@8rF5fXsxxB6W=*5qIm?-A<$U2 zUYKtri-$5Fr%f-1^bh2otW`_NDA%o1RYqcNy4nc;{cAm3>L?PJ9;)k&l~gr=J7n@e z@We*N6M>)}9`1c0uL$K)IxK=f$o*i%SJe!ayr9X|Jmp_J2tkMJiQ;Mav*-_#5wji) zrg*m@oBBUG`Rnuc#(31*gL_Yf0#f$Q1w{YD?EmsSL1Qr4F_RN(CB!2sw>8{^@E%IK zH}wYC;r%{mW??~0a(|!S``$lZ6n1y^%$ZZ3 z^YmgBT5WhA5J4oCKqNc|5scKM77YCX;@9y{4{~*;lQII}Fgy(jcRycl^ zW%Pj9(D1N^jUtGDV(c;2#57Fyg64bhF^W7F!0e692#3apvuS`UnJV4^`;#xTJVJOl z=ZNGAr1}Tg!PB|Ux4Mc7%5*=gpYV1L1WiSz9asU(2-7NQ+pb6P+O@?!+O?Ihvww9f z-q!!L)-F17pRb?sd_%j8)^~fV%$B)sJUsnSDJnQP zI5apkC=~HsP`|oDqU(6Txz{%)7OF+wZCu)KIXT$JEfCm7DCe*b6rTX#TL!iUgk8iZ zkakwoYR1=8F$0G|ub5#PQPFDHuwlx%ip}UFuu4<*D{j!mqmBpAp)(GSGFQmZ^)k7S z9nMmU2wA5J8KkVG%p6WK#EJ^qw0UPq>4fox1!o_9>=mRT!*e$m7B1`5=_gdd;{4?2 zpVI!x@TL4k>8|A9-T#9BqM?0MbCPn9cfYnfw{m5ak_UHDE~mxJ`c7v>%af!U}_pl=)(#FCbdl z3J#W<@jl3D7=1%CLY&{;X~oSu7Jz7L z0uZ=F34$m1_&{**h}J^JKYp~=vO#ULTSP}SbdPN08}8+~;pru>Pix;SF4b3Q91{{+ zWd1jesp>Z#qaNkQ03UzqJ7(;>dbGSFKSmMBG@lP08~Av058NA+5g=R>L35-z_!XWY zbPtxEk1sR#_^r45QMw0(cQ%OUsnXxtJ6))kBg*7>9#b(zwNvcpZ;lOe1jhWgVuS>A z0RU3biDNuJR(@SsuaBRvrnzJMfA=f{?qLK|k5+zIhDA{A6;m)JeGTOZ(GTVXbO5VR zT`)(xYIp>&r`jxXMCFHkv$;q4^)f}bW9e¥cgBtkoSPobvGVdSYa$QZA-f#y8U< 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 literal 0 HcmV?d00001 diff --git a/Apps/Dependencies/DroidSans-APACHE-2.0.txt b/Apps/Dependencies/DroidSans-APACHE-2.0.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Apps/Dependencies/DroidSans-APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Apps/Dependencies/DroidSans-PROVENANCE.txt b/Apps/Dependencies/DroidSans-PROVENANCE.txt new file mode 100644 index 000000000..030289d2c --- /dev/null +++ b/Apps/Dependencies/DroidSans-PROVENANCE.txt @@ -0,0 +1,13 @@ +DroidSans.ttf + +Source repository: https://github.com/CedricGuillemet/dump +Source commit: 3937c4fe7a24eb5352e529a06604c2a94c9d1bc7 +Source file: droidsans.ttf +Immutable URL: https://raw.githubusercontent.com/CedricGuillemet/dump/3937c4fe7a24eb5352e529a06604c2a94c9d1bc7/droidsans.ttf +SHA-256: 4E2371BC0E4CF6983342E150412F140DA79D674C9BE0B56458401F581072ECD3 + +Font name metadata: +Digitized data copyright 2007, Google Corporation. +Version 1.00 build 113. +Licensed under the Apache License, Version 2.0. +See DroidSans-APACHE-2.0.txt. diff --git a/Apps/Dependencies/DroidSans.ttf b/Apps/Dependencies/DroidSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..767c63ad000e3eea20f3cb7a43ba9f4154ed7a5d GIT binary patch literal 190044 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%0D=-PBu&{ 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, reject) => { + RequestFile( + "app:///Assets/Arimo-Regular.ttf", + (data) => { + if (typeof data === "string") { + reject(new Error("Expected binary font data")); + } else { + resolve(data); + } + }, + undefined, + undefined, + true, + (error) => reject(error) + ); + }); + _native.Canvas.loadTTF("arimo-regular", fontData); + + const ctx = createContext(); + ctx.font = "18px arimo-regular"; + expect(ctx.measureText("Home Impulse").width).to.be.closeTo(116.0419921875, 0.0001); + expect(ctx.measureText("Far Away Impulse").width).to.be.closeTo(143.71875, 0.0001); + expect(ctx.measureText("Move far away").width).to.be.closeTo(117.0439453125, 0.0001); + expect(ctx.measureText("Hg").fontBoundingBoxAscent).to.equal(16); + expect(ctx.measureText("Hg").fontBoundingBoxDescent).to.equal(5); + ctx.letterSpacing = "0.25px"; + expect(ctx.measureText("Home Impulse").width).to.be.closeTo(118.7919921875, 0.0001); + }); function createCanvas(width: number, height: number): any { const canvas = new _native.Canvas(); canvas.width = width; diff --git a/Apps/UnitTests/Source/Tests.JavaScript.cpp b/Apps/UnitTests/Source/Tests.JavaScript.cpp index 1754e7748..7dd90ad4f 100644 --- a/Apps/UnitTests/Source/Tests.JavaScript.cpp +++ b/Apps/UnitTests/Source/Tests.JavaScript.cpp @@ -15,6 +15,9 @@ #ifdef HAS_NATIVE_MESHOPT #include #endif +#ifdef HAS_NATIVE_OPTIMIZATIONS +#include +#endif #include #ifdef HAS_NATIVE_IMAGE_LOADING #include @@ -165,8 +168,10 @@ TEST(JavaScript, All) #endif #if defined(SKIP_RENDER_TESTS) || defined(SKIP_EXTERNAL_TEXTURE_TESTS) env.Global().Set("skipCanvasGpuTests", true); + env.Global().Set("hasGpuRendering", false); #else env.Global().Set("skipCanvasGpuTests", false); + env.Global().Set("hasGpuRendering", true); #endif Babylon::Polyfills::XMLHttpRequest::Initialize(env); @@ -184,6 +189,9 @@ TEST(JavaScript, All) #ifdef HAS_NATIVE_MESHOPT Babylon::Plugins::NativeMeshopt::Initialize(env); #endif +#ifdef HAS_NATIVE_OPTIMIZATIONS + Babylon::Plugins::NativeOptimizations::Initialize(env); +#endif auto setExitCodeCallback = Napi::Function::New( env, [&exitCodePromise](const Napi::CallbackInfo& info) { diff --git a/Polyfills/Canvas/CMakeLists.txt b/Polyfills/Canvas/CMakeLists.txt index 909047907..10825441b 100644 --- a/Polyfills/Canvas/CMakeLists.txt +++ b/Polyfills/Canvas/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES "Source/Gradient.h" "Source/Font.cpp" "Source/Font.h" + "Source/StbTrueType.cpp" "Source/nanosvg.h" "Source/nanovg/nanovg.cpp" "Source/nanovg/nanovg.h" diff --git a/Polyfills/Canvas/Readme.md b/Polyfills/Canvas/Readme.md index c7a06389f..e0c8022be 100644 --- a/Polyfills/Canvas/Readme.md +++ b/Polyfills/Canvas/Readme.md @@ -30,6 +30,11 @@ the interpolation border, and fontstash blur padding are excluded from those bounds. The no-font fallback exposes the same metric properties, but its values are estimates rather than measurements of a loaded face. +`actualBoundingBox*` describes glyph ink. GUI line layout should use +`fontBoundingBox*`, which preserves browser-compatible integral line metrics. +Text regressions use the packaged Arimo font; its license and provenance ship +with the font. + The Canvas pixel regressions in `JavaScript.All` require a rendering GPU backend. The test host sets `hasGpuRendering` to false only for the explicit no-op Metal test configuration (`BABYLON_NATIVE_TESTS_USE_NOOP_METAL_DEVICE=ON`), using the diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 0800eebdc..4617c2778 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -758,8 +758,9 @@ namespace Babylon::Polyfills::Internal { std::string text{info[0].As()}; - // Measure against the same face FillText will bind. + // SetFont records the face but does not bind it; measure with the same face as FillText. const bool fontBound = SetFontFaceId(); + // No face available: synthesize Arial-ish metrics so callers still get a finite width. if (!fontBound && m_state.font.Size() > 0.f) { diff --git a/Polyfills/Canvas/Source/MeasureText.cpp b/Polyfills/Canvas/Source/MeasureText.cpp index dc7d782ab..cd902251e 100644 --- a/Polyfills/Canvas/Source/MeasureText.cpp +++ b/Polyfills/Canvas/Source/MeasureText.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "Canvas.h" #include "Context.h" @@ -30,6 +31,11 @@ namespace Babylon::Polyfills::Internal // of the alignment baseline. const float inkAscent = -bounds[1]; const float inkDescent = bounds[3]; + // Keep the browser-compatible integral CSS line box used by Babylon GUI. + // The raw font metrics are fractional em-scaled values; Canvas layout rounds + // the baseline and total line height independently. + const float fontAscent = std::floor(textMetrics[0]); + const float fontDescent = std::ceil(textMetrics[2]) - fontAscent; auto obj{Napi::Object::New(env)}; obj.Set("width", Napi::Value::From(env, advance)); @@ -39,8 +45,8 @@ namespace Babylon::Polyfills::Internal obj.Set("actualBoundingBoxRight", Napi::Value::From(env, bounds[2])); obj.Set("actualBoundingBoxAscent", Napi::Value::From(env, inkAscent)); obj.Set("actualBoundingBoxDescent", Napi::Value::From(env, inkDescent)); - obj.Set("fontBoundingBoxAscent", Napi::Value::From(env, textMetrics[0])); - obj.Set("fontBoundingBoxDescent", Napi::Value::From(env, -textMetrics[1])); + obj.Set("fontBoundingBoxAscent", Napi::Value::From(env, fontAscent)); + obj.Set("fontBoundingBoxDescent", Napi::Value::From(env, fontDescent)); return obj.As(); } diff --git a/Polyfills/Canvas/Source/StbTrueType.cpp b/Polyfills/Canvas/Source/StbTrueType.cpp new file mode 100644 index 000000000..8ee3ce6a1 --- /dev/null +++ b/Polyfills/Canvas/Source/StbTrueType.cpp @@ -0,0 +1,208 @@ +#define STB_TRUETYPE_IMPLEMENTATION +#include +#undef STB_TRUETYPE_IMPLEMENTATION + +namespace +{ + constexpr stbtt_uint32 Tag(char a, char b, char c, char d) + { + return (static_cast(a) << 24) | + (static_cast(b) << 16) | + (static_cast(c) << 8) | + static_cast(d); + } + + stbtt_uint32 ReadTag(stbtt_uint8* value) + { + return (static_cast(value[0]) << 24) | + (static_cast(value[1]) << 16) | + (static_cast(value[2]) << 8) | + static_cast(value[3]); + } + + stbtt_uint32 ScriptTagForCodepoint(unsigned int codepoint) + { + if (codepoint <= 0x024f) + { + return Tag('l', 'a', 't', 'n'); + } + if (codepoint >= 0x0370 && codepoint <= 0x03ff) + { + return Tag('g', 'r', 'e', 'k'); + } + if (codepoint >= 0x0400 && codepoint <= 0x052f) + { + return Tag('c', 'y', 'r', 'l'); + } + if (codepoint >= 0x0590 && codepoint <= 0x05ff) + { + return Tag('h', 'e', 'b', 'r'); + } + return 0; + } + + int GetPairAdjustment(stbtt_uint8* lookupList, int lookupIndex, int glyph1, int glyph2) + { + const auto lookupCount = ttUSHORT(lookupList); + if (lookupIndex < 0 || lookupIndex >= lookupCount) + { + return 0; + } + + auto* lookupTable = lookupList + ttUSHORT(lookupList + 2 + 2 * lookupIndex); + if (ttUSHORT(lookupTable) != 2) + { + return 0; + } + + const auto subTableCount = ttUSHORT(lookupTable + 4); + auto* subTableOffsets = lookupTable + 6; + for (int subTableIndex = 0; subTableIndex < subTableCount; ++subTableIndex) + { + auto* table = lookupTable + ttUSHORT(subTableOffsets + 2 * subTableIndex); + const auto coverageIndex = stbtt__GetCoverageIndex(table + ttUSHORT(table + 2), glyph1); + if (coverageIndex == -1) + { + continue; + } + + const auto valueFormat1 = ttUSHORT(table + 4); + const auto valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 != 4 || valueFormat2 != 0) + { + continue; + } + + switch (ttUSHORT(table)) + { + case 1: + { + const auto pairSetCount = ttUSHORT(table + 8); + if (coverageIndex >= pairSetCount) + { + continue; + } + + auto* pairValueTable = table + ttUSHORT(table + 10 + 2 * coverageIndex); + const auto pairValueCount = ttUSHORT(pairValueTable); + auto* pairValueArray = pairValueTable + 2; + int left = 0; + int right = pairValueCount - 1; + while (left <= right) + { + const int middle = (left + right) >> 1; + auto* pairValue = pairValueArray + 4 * middle; + const int secondGlyph = ttUSHORT(pairValue); + if (glyph2 < secondGlyph) + { + right = middle - 1; + } + else if (glyph2 > secondGlyph) + { + left = middle + 1; + } + else + { + return ttSHORT(pairValue + 2); + } + } + break; + } + case 2: + { + const int glyph1Class = stbtt__GetGlyphClass(table + ttUSHORT(table + 8), glyph1); + const int glyph2Class = stbtt__GetGlyphClass(table + ttUSHORT(table + 10), glyph2); + const auto class1Count = ttUSHORT(table + 12); + const auto class2Count = ttUSHORT(table + 14); + if (glyph1Class < 0 || glyph1Class >= class1Count || glyph2Class < 0 || glyph2Class >= class2Count) + { + continue; + } + + auto* class1Records = table + 16; + auto* class2Records = class1Records + 2 * (glyph1Class * class2Count); + return ttSHORT(class2Records + 2 * glyph2Class); + } + } + } + + return 0; + } + + int GetScriptKernAdvance(const stbtt_fontinfo* font, int glyph1, int glyph2, stbtt_uint32 scriptTag) + { + if (!font->gpos || !scriptTag) + { + return stbtt_GetGlyphKernAdvance(font, glyph1, glyph2); + } + + auto* gpos = font->data + font->gpos; + if (ttUSHORT(gpos) != 1) + { + return stbtt_GetGlyphKernAdvance(font, glyph1, glyph2); + } + + // stb_truetype scans every pair lookup and can apply one from the wrong OpenType script. + auto* scriptList = gpos + ttUSHORT(gpos + 4); + const auto scriptCount = ttUSHORT(scriptList); + stbtt_uint8* script = nullptr; + for (int scriptIndex = 0; scriptIndex < scriptCount; ++scriptIndex) + { + auto* scriptRecord = scriptList + 2 + 6 * scriptIndex; + if (ReadTag(scriptRecord) == scriptTag) + { + script = scriptList + ttUSHORT(scriptRecord + 4); + break; + } + } + if (!script) + { + return stbtt_GetGlyphKernAdvance(font, glyph1, glyph2); + } + + const auto defaultLangSysOffset = ttUSHORT(script); + if (!defaultLangSysOffset) + { + return stbtt_GetGlyphKernAdvance(font, glyph1, glyph2); + } + + auto* langSys = script + defaultLangSysOffset; + auto* featureList = gpos + ttUSHORT(gpos + 6); + const auto featureCount = ttUSHORT(featureList); + auto* lookupList = gpos + ttUSHORT(gpos + 8); + const auto langFeatureCount = ttUSHORT(langSys + 4); + for (int langFeatureIndex = -1; langFeatureIndex < langFeatureCount; ++langFeatureIndex) + { + const int featureIndex = langFeatureIndex < 0 ? ttUSHORT(langSys + 2) : ttUSHORT(langSys + 6 + 2 * langFeatureIndex); + if (featureIndex == 0xffff || featureIndex >= featureCount) + { + continue; + } + + auto* featureRecord = featureList + 2 + 6 * featureIndex; + if (ReadTag(featureRecord) != Tag('k', 'e', 'r', 'n')) + { + continue; + } + + auto* feature = featureList + ttUSHORT(featureRecord + 4); + const auto lookupIndexCount = ttUSHORT(feature + 2); + for (int index = 0; index < lookupIndexCount; ++index) + { + const int adjustment = GetPairAdjustment(lookupList, ttUSHORT(feature + 4 + 2 * index), glyph1, glyph2); + if (adjustment) + { + return adjustment; + } + } + return 0; + } + + return 0; + } +} + +int fons__stb_getGlyphKernAdvanceForCodepoint(const stbtt_fontinfo* font, int glyph1, int glyph2, unsigned int codepoint) +{ + return GetScriptKernAdvance(font, glyph1, glyph2, ScriptTagForCodepoint(codepoint)); +} diff --git a/Polyfills/Canvas/Source/nanovg/fontstash.h b/Polyfills/Canvas/Source/nanovg/fontstash.h index cd78be0dd..9cb2cee6b 100644 --- a/Polyfills/Canvas/Source/nanovg/fontstash.h +++ b/Polyfills/Canvas/Source/nanovg/fontstash.h @@ -196,7 +196,7 @@ void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, i *lineGap = font->font->height - (*ascent - *descent); } -// CSS/canvas font-size is em size (map em square → pixels), not ascent+descent. +// CSS font sizes scale the em square, not ascent plus descent. float fons__tt_getEmUnits(FONSttFontImpl *font) { return (float)font->font->units_per_EM; @@ -258,9 +258,10 @@ void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int } } -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2, unsigned int codepoint) { FT_Vector ftKerning; + FONS_NOTUSED(codepoint); FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer } @@ -281,6 +282,8 @@ static void fons__tmpfree(void* ptr, void* up); #define STBTT_DEF extern #include +int fons__stb_getGlyphKernAdvanceForCodepoint(const stbtt_fontinfo* font, int glyph1, int glyph2, unsigned int codepoint); + struct FONSttFontImpl { stbtt_fontinfo font; }; @@ -313,7 +316,7 @@ void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, i stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); } -// CSS/canvas font-size is em size (map em square → pixels). +// Match the em-based scaling of the FreeType implementation. float fons__tt_getEmUnits(FONSttFontImpl *font) { const float scale = stbtt_ScaleForMappingEmToPixels(&font->font, 1.0f); @@ -364,9 +367,9 @@ void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int #endif } -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2, unsigned int codepoint) { - return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); + return fons__stb_getGlyphKernAdvanceForCodepoint(&font->font, glyph1, glyph2, codepoint); } #endif @@ -424,7 +427,8 @@ struct FONSglyph int next; short size, blur; short x0,y0,x1,y1; - short xadv,xoff,yoff; + float xadv; + short xoff,yoff; }; typedef struct FONSglyph FONSglyph; @@ -992,9 +996,7 @@ int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, in stash->nscratch = 0; if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; - // Store normalized line height. The real line height is got - // by multiplying the lineh by font size. - // Normalize metrics against em square (matches pixel height scale). + // Normalize line metrics against the em square, matching glyph rasterization. fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); fh = ascent - descent; em = fons__tt_getEmUnits(&font->font); @@ -1188,7 +1190,7 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in glyph->y0 = (short)gy; glyph->x1 = (short)(glyph->x0+gw); glyph->y1 = (short)(glyph->y0+gh); - glyph->xadv = (short)(scale * advance * 10.0f); + glyph->xadv = scale * advance; glyph->xoff = (short)(x0 - pad); glyph->yoff = (short)(y0 - pad); @@ -1243,8 +1245,10 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, float rx,ry,xoff,yoff,x0,y0,x1,y1; if (prevGlyphIndex != -1) { - float adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index) * scale; - *x += (int)(adv + spacing + 0.5f); + float adv = 0.0f; + if (glyph->codepoint != ' ' && prevGlyphIndex != fons__tt_getGlyphIndex(&font->font, ' ')) + adv = fons__tt_getGlyphKernAdvance(&font->font, prevGlyphIndex, glyph->index, glyph->codepoint) * scale; + *x += adv + spacing; } // Each glyph has 2px border to allow good interpolation, @@ -1258,8 +1262,8 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, y1 = (float)(glyph->y1-1); if (stash->params.flags & FONS_ZERO_TOPLEFT) { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y + yoff); + rx = *x + xoff; + ry = *y + yoff; q->x0 = rx; q->y0 = ry; @@ -1271,8 +1275,8 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, q->s1 = x1 * stash->itw; q->t1 = y1 * stash->ith; } else { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y - yoff); + rx = *x + xoff; + ry = *y - yoff; q->x0 = rx; q->y0 = ry; @@ -1285,7 +1289,7 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, q->t1 = y1 * stash->ith; } - *x += (int)(glyph->xadv / 10.0f + 0.5f); + *x += glyph->xadv; } static void fons__flush(FONScontext* stash) From 2b271dba5f9a3766d3d0a291051a77b6100e3fd8 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:20 -0700 Subject: [PATCH 09/16] Preserve Canvas paths and compound clip masks Preserve the caller's current path across immediate drawing operations and save/restore clip state correctly. Normalize signed rectangular clips and rasterize curved or compound paths into antialiased masks consumed by NanoVG draws. Update shader sources and compiled backends together, with nested-clip and path-preservation regressions. Review-Group: N6 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 6181f3efcacb1fae23b375a973b62c6c1560ced9 Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 320 ++++ .../Source/Tests.Canvas.Readback.cpp | 64 + Polyfills/Canvas/CMakeLists.txt | 1 + Polyfills/Canvas/Source/Context.cpp | 118 +- Polyfills/Canvas/Source/Context.h | 16 +- .../Canvas/Source/NativeInstanceRegistry.h | 2 +- .../Source/Shaders/dxbc/fs_nanovg_fill.h | 368 +++-- .../Source/Shaders/dxil/fs_nanovg_fill.h | 595 +++---- .../Source/Shaders/essl/fs_nanovg_fill.h | 525 +++--- .../Canvas/Source/Shaders/fs_nanovg_fill.sc | 8 + .../Source/Shaders/glsl/fs_nanovg_fill.h | 597 +++---- .../Source/Shaders/metal/fs_nanovg_fill.h | 636 ++++---- .../Source/Shaders/spirv/fs_nanovg_fill.h | 875 +++++----- Polyfills/Canvas/Source/nanosvgrast.h | 1452 +++++++++++++++++ Polyfills/Canvas/Source/nanovg/nanovg.cpp | 106 ++ Polyfills/Canvas/Source/nanovg/nanovg.h | 11 + .../Canvas/Source/nanovg/nanovg_babylon.cpp | 113 +- .../Source/nanovg/nanovg_filterstack.cpp | 3 +- 18 files changed, 4074 insertions(+), 1736 deletions(-) create mode 100644 Polyfills/Canvas/Source/nanosvgrast.h diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index e75acd7d2..2eeebc860 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -388,6 +388,326 @@ describe("Canvas2D", function () { } }); + (skipCanvasGpuTests ? it.skip : it)("intersects nested clips and restores parent clips on the GPU", async function () { + this.timeout(10000); + for (const translated of [false, true]) { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture("nested clips", 64, scene, false); + const ctx = texture.getContext(); + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(8, 0, 24, 64); + ctx.clip(); + + ctx.save(); + if (translated) { + ctx.translate(16, 0); + } + ctx.beginPath(); + ctx.rect(0, 0, 64, 64); + ctx.clip(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + + ctx.fillStyle = "#00ff00"; + ctx.fillRect(24, 0, 16, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(40, 0, 16, 64); + ctx.clip(); + ctx.fillStyle = "magenta"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.restore(); + + ctx.fillStyle = "blue"; + ctx.fillRect(40, 0, 8, 64); + texture.update(false); + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for the canvas texture"); + } + const pixel = (x: number) => Array.from(pixels.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4)); + expect(pixel(4), "outside parent").to.deep.equal([255, 255, 255, 255]); + expect(pixel(12), "translated child boundary").to.deep.equal(translated ? [255, 255, 255, 255] : [255, 0, 0, 255]); + expect(pixel(20), "inside intersection").to.deep.equal([255, 0, 0, 255]); + expect(pixel(28), "restored parent").to.deep.equal([0, 255, 0, 255]); + expect(pixel(36), "outside restored parent").to.deep.equal([255, 255, 255, 255]); + expect(pixel(44), "restored unclipped state").to.deep.equal([0, 0, 255, 255]); + expect(pixel(52), "disjoint clip").to.deep.equal([255, 255, 255, 255]); + expect(pixel(60), "outside every fill").to.deep.equal([255, 255, 255, 255]); + } finally { + scene.dispose(); + engine.dispose(); + } + } + }); + + (skipCanvasGpuTests ? it.skip : it)("normalizes negative rectangle dimensions before intersecting GPU clips", async function () { + this.timeout(10000); + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture("signed clips", 64, scene, false); + const ctx = texture.getContext(); + for (const rotated of [false, true]) { + let expected: Uint8Array | undefined; + for (const [flipX, flipY] of [[false, false], [true, false], [false, true], [true, true]]) { + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(18, 12, 36, 44); + ctx.clip(); + ctx.save(); + if (rotated) { + ctx.translate(32, 32); + ctx.rotate(0.3); + ctx.translate(-32, -32); + } + ctx.beginPath(); + ctx.rect(flipX ? 40 : 16, flipY ? 40 : 20, flipX ? -24 : 24, flipY ? -20 : 20); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.restore(); + texture.update(false); + + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for signed clips"); + } + const inside = (30 * 64 + 28) * 4; + expect(Array.from(pixels.subarray(inside, inside + 4)), `inside clip, rotated=${rotated}, flipX=${flipX}, flipY=${flipY}`).to.deep.equal([0, 0, 255, 255]); + expect(Array.from(pixels.subarray(0, 4)), "outside parent clip").to.deep.equal([255, 255, 255, 255]); + if (expected) { + let changed = 0; + for (let index = 0; index < pixels.length; ++index) { + if (pixels[index] !== expected[index]) { + ++changed; + } + } + expect(changed, `signed clip equivalence, rotated=${rotated}, flipX=${flipX}, flipY=${flipY}`).to.equal(0); + } else { + expected = pixels.slice(); + } + } + } + } finally { + scene.dispose(); + engine.dispose(); + } + }); + + (skipCanvasGpuTests ? it.skip : it)("clips text, fills, strokes and images to saved curved paths on the GPU", async function () { + this.timeout(20000); + const fontData = await new Promise((resolve, reject) => { + RequestFile("app:///Assets/Arimo-Regular.ttf", data => { + if (typeof data === "string") { + reject(new Error("Expected binary clip-test font")); + } else { + resolve(data); + } + }, undefined, undefined, true, reject); + }); + _native.Canvas.loadTTF("clip-test-font", fontData); + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture("curved clips", 64, scene, false); + const source = new DynamicTexture("clip image", 64, scene, false); + source.getContext().fillStyle = "blue"; + source.getContext().fillRect(0, 0, 64, 64); + const ctx = texture.getContext(); + const read = async () => { + texture.update(false); + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for curved clips"); + } + // Native readback is bottom-up; assertions below use Canvas coordinates. + const topDown = new Uint8Array(pixels.length); + for (let y = 0; y < 64; ++y) { + topDown.set(pixels.subarray(y * 64 * 4, (y + 1) * 64 * 4), (63 - y) * 64 * 4); + } + return topDown; + }; + for (const mode of ["text", "fill", "stroke", "image", "clear", "blur(1px)", "blur(3px)"]) { + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.translate(32, 32); + ctx.rotate(0.2); + ctx.beginPath(); + ctx.arc(0, 0, 20, 0, Math.PI * 2); + ctx.clip(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + // A new path and a GPU flush must not erase the saved clipping region. + ctx.beginPath(); + await read(); + ctx.fillStyle = "blue"; + ctx.strokeStyle = "blue"; + if (mode === "text") { + ctx.font = "24px clip-test-font"; + ctx.fillText("MMMM", 0, 24); + ctx.fillText("MMMM", 0, 48); + } else if (mode === "stroke") { + ctx.lineWidth = 12; + ctx.moveTo(0, 32); + ctx.lineTo(64, 32); + ctx.stroke(); + } else if (mode === "image") { + ctx.drawImage(source.getContext().canvas, 0, 0); + } else if (mode === "clear") { + ctx.clearRect(0, 0, 64, 64); + } else { + if (mode.startsWith("blur")) { + ctx.filter = mode; + } + ctx.fillRect(0, 0, 64, 64); + } + const pixels = await read(); + let changedInside = 0; + for (let y = 0; y < 64; ++y) { + for (let x = 0; x < 64; ++x) { + const offset = (y * 64 + x) * 4; + const changed = pixels.subarray(offset, offset + 4).some(value => value !== 255); + const distance = Math.hypot(x + 0.5 - 32, y + 0.5 - 32); + if (distance > 22) { + expect(changed, `${mode} outside curved clip at ${x},${y}`).to.equal(false); + } else if (distance < 18 && changed) { + ++changedInside; + } + } + } + expect(changedInside, `${mode} visible inside clip`).to.be.greaterThan(50); + ctx.restore(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 4, 4); + const restored = await read(); + expect(Array.from(restored.subarray(0, 4)), `${mode} restore after multiple flushes`).to.deep.equal([255, 0, 0, 255]); + } + + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.roundRect(8, 8, 48, 48, 16); + ctx.clip(); + ctx.save(); + ctx.beginPath(); + ctx.arc(24, 32, 14, 0, Math.PI * 2); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.fillStyle = "#00ff00"; + ctx.fillRect(40, 8, 16, 48); + ctx.restore(); + const nested = await read(); + const pixel = (x: number, y: number) => Array.from(nested.subarray((y * 64 + x) * 4, (y * 64 + x + 1) * 4)); + expect(pixel(24, 32), "nested curved intersection").to.deep.equal([0, 0, 255, 255]); + expect(pixel(48, 32), "restored rounded parent").to.deep.equal([0, 255, 0, 255]); + expect(pixel(54, 10), "rounded corner stays clipped").to.deep.equal([255, 255, 255, 255]); + expect(pixel(32, 12), "outside nested circle").to.deep.equal([255, 255, 255, 255]); + + for (const mode of ["nonzero", "evenodd", "opposite", "empty", "zero", "Path2D"]) { + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + if (mode === "zero") { + ctx.rect(8, 8, 0, 48); + } else if (mode !== "empty") { + ctx.rect(8, 8, 48, 48); + if (mode === "opposite") { + ctx.rect(48, 16, -32, 32); + } else { + ctx.rect(16, 16, 32, 32); + } + } + if (mode === "Path2D") { + const path = new _native.Path2D(); + path.arc(32, 32, 20, 0, Math.PI * 2); + ctx.clip(path); + path.rect(0, 0, 64, 64); + } else { + ctx.clip(mode === "evenodd" ? "evenodd" : "nonzero"); + } + // Immediate rectangles must not replace the path captured above. + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + const result = await read(); + const center = (32 * 64 + 32) * 4; + const filled = mode === "nonzero" || mode === "Path2D"; + expect(Array.from(result.subarray(center, center + 4)), `${mode} clip center`).to.deep.equal(filled ? [0, 0, 255, 255] : [255, 255, 255, 255]); + expect(Array.from(result.subarray(0, 4)), `${mode} clip outside`).to.deep.equal([255, 255, 255, 255]); + ctx.restore(); + } + + ctx.beginPath(); + ctx.arc(32, 32, 20, 0, Math.PI * 2); + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.strokeRect(0, 0, 2, 2); + ctx.clearRect(0, 0, 2, 2); + ctx.save(); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + const preserved = await read(); + const outside = (8 * 64 + 8) * 4; + expect(Array.from(preserved.subarray(outside, outside + 4)), "immediate operations preserve the curved current path").to.deep.equal([255, 255, 255, 255]); + const center = (32 * 64 + 32) * 4; + expect(Array.from(preserved.subarray(center, center + 4)), "preserved path interior").to.deep.equal([0, 0, 255, 255]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + + (skipCanvasGpuTests ? it.skip : it)("clears only the clipped GPU region and ignores globalAlpha and filters", async function () { + this.timeout(10000); + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture("clipped clear", 64, scene, false); + const ctx = texture.getContext(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 64, 64); + texture.update(false); + + ctx.filter = "blur(2px)"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(8, 0, 24, 64); + ctx.clip(); + ctx.globalAlpha = 0.25; + ctx.clearRect(0, 0, 64, 64); + ctx.restore(); + texture.update(false); + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for the canvas texture"); + } + const pixel = (x: number) => Array.from(pixels.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4)); + expect(pixel(36), "preserved near clip").to.deep.equal([255, 0, 0, 255]); + expect(pixel(16), "fully cleared inside clip").to.deep.equal([0, 0, 0, 0]); + expect(pixel(44), "preserved after clip").to.deep.equal([255, 0, 0, 255]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + (skipCanvasGpuTests ? it.skip : it)("uses the strokeRect geometry after a preceding fillRect", async function () { this.timeout(10000); const engine = new NativeEngine(); diff --git a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp index 13d28a3c3..0ef61921e 100644 --- a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp +++ b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp @@ -14,6 +14,7 @@ #include "../../../Polyfills/Canvas/Source/NativeInstanceRegistry.h" #include "../../../Polyfills/Canvas/Source/Path2D.h" #include "../../../Polyfills/Canvas/Source/nanovg/nanovg.h" +#include "../../../Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h" #include #include @@ -127,6 +128,69 @@ TEST(CanvasReadback, NativeBrandsRequireTheOriginalReceiver) }); } +TEST(CanvasReadback, ImageCallbacksAreJavaScriptOwned) +{ + RunCanvasTest([](Napi::Env env) { + const auto constructor = Babylon::JsRuntime::NativeObject::GetFromJavaScript(env).Get("Image").As(); + auto image = constructor.New({}); + const auto object = env.Global().Get("Object").As(); + const auto descriptor = object.Get("getOwnPropertyDescriptor").As(); + const auto callback = Napi::Function::New(env, [](const Napi::CallbackInfo&) {}); + for (const auto* event : {"onload", "onerror"}) + { + EXPECT_TRUE(image.Get(event).IsNull()); + image.Set(event, callback); + const auto property = descriptor.Call(object, {image, Napi::String::New(env, event)}).As(); + EXPECT_EQ(property.Get("value"), callback); + image.Set(event, env.Null()); + EXPECT_TRUE(image.Get(event).IsNull()); + } + }); +} + +TEST(CanvasReadback, GaussianBlurPadsUniformUploads) +{ + RunCanvasTest([](Napi::Env env) { + const auto constructor = Babylon::JsRuntime::NativeObject::GetFromJavaScript(env).Get("Canvas").As(); + auto canvas = constructor.New({}); + canvas.Get("getContext").As().Call(canvas, {Napi::String::New(env, "2d")}); + auto* nativeCanvas = Babylon::Polyfills::Internal::NativeCanvas::Unwrap(canvas); + nativeCanvas->UpdateRenderTarget(); + auto* frameBuffer = &nativeCanvas->GetFrameBuffer(); + nanovg_filterstack filters; + filters.ParseString("blur(1px)"); + size_t uploads{}; + filters.Render(BGFX_INVALID_HANDLE, + [&](bgfx::UniformHandle handle, const void* data, uint16_t count) { + if (handle.idx != nanovg_filterstack::m_uniforms.u_weights.idx) + { + return; + } + ++uploads; + ASSERT_EQ(count, 5u); + const auto* weights = static_cast(data); + float sum{}; + for (size_t i = 0; i < 13; ++i) + { + sum += weights[i]; + EXPECT_FLOAT_EQ(weights[i], weights[12 - i]); + } + EXPECT_NEAR(sum, 1.0f, 1e-6f); + for (size_t i = 13; i < count * 4u; ++i) + { + EXPECT_FLOAT_EQ(weights[i], 0.0f); + } + }, + [](bgfx::ProgramHandle, Babylon::Graphics::FrameBuffer*) {}, + [](bgfx::ProgramHandle, Babylon::Graphics::FrameBuffer*, Babylon::Graphics::FrameBuffer*) {}, + [](bgfx::ProgramHandle, Babylon::Graphics::FrameBuffer*, Babylon::Graphics::FrameBuffer*) {}, + frameBuffer, + [frameBuffer] { return frameBuffer; }, + [](Babylon::Graphics::FrameBuffer*) {}); + EXPECT_EQ(uploads, 2u); + }); +} + #ifdef HAS_NATIVE_IMAGE_LOADING TEST(CanvasImages, SvgUsesBimgParserAndRgbaPixels) { diff --git a/Polyfills/Canvas/CMakeLists.txt b/Polyfills/Canvas/CMakeLists.txt index 10825441b..99600c220 100644 --- a/Polyfills/Canvas/CMakeLists.txt +++ b/Polyfills/Canvas/CMakeLists.txt @@ -29,6 +29,7 @@ set(SOURCES "Source/Font.h" "Source/StbTrueType.cpp" "Source/nanosvg.h" + "Source/nanosvgrast.h" "Source/nanovg/nanovg.cpp" "Source/nanovg/nanovg.h" "Source/nanovg/nanovg_babylon.cpp" diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 4617c2778..484791e54 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -28,9 +28,7 @@ #pragma GCC diagnostic pop #endif -#define STB_TRUETYPE_IMPLEMENTATION #include "stb/stb_truetype.h" -#undef STB_TRUETYPE_IMPLEMENTATION #include "Canvas.h" #include "Context.h" @@ -50,6 +48,12 @@ namespace Babylon::Polyfills::Internal { static constexpr auto JS_CONTEXT_CONSTRUCTOR_NAME = "Context"; + struct Context::ClipMask + { + NVGclipMask mask; + int image{}; + }; + namespace { class ScopedPath @@ -286,14 +290,7 @@ namespace Babylon::Polyfills::Internal auto width = info[2].As().FloatValue(); auto height = info[3].As().FloatValue(); - // fillRect neither reads nor modifies the current path per spec, so it - // would normally start its own. But Clip() can only express a rectangle - // (nvgScissor), so a non-rectangular clip path is emulated by leaving it - // in the current path and letting this fill render it. See Clip(). - if (!m_isClipped) - { - nvgBeginPath(*m_nvg); - } + ScopedPath temporaryPath{*m_nvg}; nvgRect(*m_nvg, left, top, width, height); @@ -398,9 +395,10 @@ namespace Babylon::Polyfills::Internal ValidateFillRule(info.Env(), info[1]); } - // draw Path2D if exists + std::optional temporaryPath; if (path != nullptr) { + temporaryPath.emplace(*m_nvg); PlayPath2D(path); } @@ -425,7 +423,6 @@ namespace Babylon::Polyfills::Internal void Context::Restore(const Napi::CallbackInfo&) { nvgRestore(*m_nvg); - m_isClipped = false; if (!m_savedStates.empty()) { m_state = std::move(m_savedStates.back()); @@ -447,10 +444,7 @@ namespace Babylon::Polyfills::Internal nanovg_filterstack clearFilters; nvgFilterStack(*m_nvg, clearFilters); - // See FillRect: clipping is a scissor, so the path must always be reset. Resetting it - // invalidates the emulated clip, which points at a path that no longer exists, and the - // nvgRestore below only pops ctx->states -- it does not put the old path back. - ResetPathState(); + ScopedPath temporaryPath{*m_nvg}; nvgRect(*m_nvg, x, y, width, height); @@ -488,8 +482,8 @@ namespace Babylon::Polyfills::Internal void Context::ResetPathState() { - m_isClipped = false; m_pathHasNonRect = false; + m_pathRectangleCount = 0; nvgBeginPath(*m_nvg); } @@ -507,6 +501,7 @@ namespace Babylon::Polyfills::Internal nvgRect(*m_nvg, left, top, width, height); m_rectangleClipping = {left, top, width, height}; + ++m_pathRectangleCount; } void Context::RoundRect(const Napi::CallbackInfo& info) @@ -572,35 +567,58 @@ namespace Babylon::Polyfills::Internal m_rectangleClipping = {x, y, width, height}; - // Deliberately does not set m_pathHasNonRect, even though rounded corners are not - // something nvgScissor can express. Clip()'s emulation for a non-rectangular path is - // to leave the path current and let the next fill draw it, and nanovg fills the union - // of the subpaths, not their intersection -- so `roundRect(); clip(); fillRect()` would - // paint the whole fillRect rather than the rounded region. Measured on the "Native - // Canvas" visual test: routing roundRect into the emulation takes the pixel difference - // from 1.850% to 20.980%, where the scissor's square bounding box stays at 1.850%. - // Dropping the radii is wrong, but it is the far smaller error of the two, and a - // correct fix needs real path clipping (a stencil pass in nanovg) rather than this - // union trick. + m_pathHasNonRect = true; } - void Context::Clip(const Napi::CallbackInfo& /*info*/) + void Context::Clip(const Napi::CallbackInfo& info) { - // A non-rectangular clip path cannot be expressed as a scissor rectangle. - // Emulate it by leaving the path current so the next fill draws it, and - // leave any enclosing scissor untouched rather than clipping to a - // rectangle this path never described. - if (m_pathHasNonRect) + const auto* path = info.Length() > 0 ? NativeCanvasPath2D::TryUnwrap(info.Env(), info[0]) : nullptr; + const auto ruleIndex = path != nullptr ? 1u : 0u; + const auto rule = info.Length() > ruleIndex && !info[ruleIndex].IsUndefined() ? info[ruleIndex].ToString().Utf8Value() : "nonzero"; + if (rule != "nonzero" && rule != "evenodd") { - m_isClipped = true; + throw Napi::TypeError::New(info.Env(), "Context2D.clip: expected nonzero or evenodd fill rule."); + } + std::optional temporaryPath; + if (path != nullptr) + { + temporaryPath.emplace(*m_nvg); + PlayPath2D(path); + } + if (path != nullptr || m_pathHasNonRect || m_pathRectangleCount != 1 || + m_rectangleClipping.width == 0 || m_rectangleClipping.height == 0) + { + auto clip = std::make_shared(); + nvgRasterizeClip(*m_nvg, m_canvas->GetWidth(), m_canvas->GetHeight(), rule == "evenodd", clip->mask); + auto& mask = clip->mask; + if (m_state.clipMask) + { + const auto& parent = m_state.clipMask->mask; + for (int y = 0; y < mask.height; ++y) + { + for (int x = 0; x < mask.width; ++x) + { + const auto px = mask.x + x - parent.x, py = mask.y + y - parent.y; + const auto alpha = px >= 0 && py >= 0 && px < parent.width && py < parent.height + ? parent.rgba[(size_t(py) * parent.width + px) * 4 + 3] : 0; + auto& value = mask.rgba[(size_t(y) * mask.width + x) * 4 + 3]; + value = static_cast((value * alpha + 127) / 255); + } + } + } + clip->image = nvgCreateImageRGBA(*m_nvg, mask.width, mask.height, NVG_IMAGE_NEAREST, mask.rgba.data()); + if (clip->image == 0) + { + throw Napi::Error::New(info.Env(), "Cannot allocate Canvas clip texture."); + } + m_clipMasks.push_back(clip); + m_state.clipMask = clip; + nvgClipImage(*m_nvg, clip->image, mask.x, mask.y, mask.width, mask.height); return; } - m_isClipped = false; - - //By default m_rectangleClipping is not set, in this case we use the canvas width and height. - auto w = m_rectangleClipping.width != 0 ? m_rectangleClipping.width : m_canvas->GetWidth(); - auto h = m_rectangleClipping.height != 0 ? m_rectangleClipping.height : m_canvas->GetHeight(); + const auto w = m_rectangleClipping.width; + const auto h = m_rectangleClipping.height; // Canvas rectangles can extend left/up; NanoVG scissors require positive extents. const auto left = m_rectangleClipping.left + std::min(w, 0.f); const auto top = m_rectangleClipping.top + std::min(h, 0.f); @@ -610,6 +628,18 @@ namespace Babylon::Polyfills::Internal nvgIntersectScissor(*m_nvg, left - 1, top - 1, std::abs(w) + 1, std::abs(h) + 1); } + void Context::ReleaseClipMasksAfterFlush() + { + m_clipMasks.erase(std::remove_if(m_clipMasks.begin(), m_clipMasks.end(), [this](const auto& clip) { + if (clip.use_count() != 1) + { + return false; + } + nvgDeleteImage(*m_nvg, clip->image); + return true; + }), m_clipMasks.end()); + } + void Context::StrokeRect(const Napi::CallbackInfo& info) { const auto left = info[0].As().FloatValue(); @@ -617,7 +647,7 @@ namespace Babylon::Polyfills::Internal const auto width = info[2].As().FloatValue(); const auto height = info[3].As().FloatValue(); - ResetPathState(); + ScopedPath temporaryPath{*m_nvg}; nvgRect(*m_nvg, left, top, width, height); BindStrokeStyle(info); SetFilterStack(); @@ -626,8 +656,6 @@ namespace Babylon::Polyfills::Internal void Context::PlayPath2D(const NativeCanvasPath2D* path) { - m_isClipped = false; - m_pathHasNonRect = true; nvgBeginPath(*m_nvg); for (const auto& command : *path) { @@ -714,9 +742,10 @@ namespace Babylon::Polyfills::Internal } } - // draw Path2D if exists + std::optional temporaryPath; if (path != nullptr) { + temporaryPath.emplace(*m_nvg); PlayPath2D(path); } @@ -936,11 +965,12 @@ namespace Babylon::Polyfills::Internal frameBuffer->Unbind(); }; - nvgBeginFrame(*m_nvg, float(width), float(height), 1.0f); + nvgSetViewport(*m_nvg, float(width), float(height)); nvgSetFrameBufferAndEncoder(*m_nvg, frameBuffer, encoder); nvgSetFrameBufferPool(*m_nvg, { acquire, release }); nvgEndFrame(*m_nvg); ReleaseImagesAfterFlush(); + ReleaseClipMasksAfterFlush(); frameBuffer.Unbind(); // Reserve the view id for the eventual canvas->texture blit NOW, while we are diff --git a/Polyfills/Canvas/Source/Context.h b/Polyfills/Canvas/Source/Context.h index 9c6afacc7..44a0adaf0 100644 --- a/Polyfills/Canvas/Source/Context.h +++ b/Polyfills/Canvas/Source/Context.h @@ -142,8 +142,10 @@ namespace Babylon::Polyfills::Internal // // The current path is deliberately not here: per the spec save() does not save it, so // the path-tracking members below stay outside the stack. + struct ClipMask; struct State { + std::shared_ptr clipMask; std::variant fillStyle{}; std::variant strokeStyle{}; // These four mirror state nanovg also holds. Their initial values must match what @@ -168,7 +170,7 @@ namespace Babylon::Polyfills::Internal std::string direction{"ltr"}; // 'ltr', 'rtl' float miterLimit{10.f}; float lineWidth{1.f}; - double globalAlpha{1.0}; + float globalAlpha{1.f}; float letterSpacing{0.f}; // font is worse than the getter-only cases above: currentFontId is what the text @@ -193,8 +195,9 @@ namespace Babylon::Polyfills::Internal // Set once the current path contains anything nvgScissor cannot express. bool m_pathHasNonRect{false}; - // Set when clip() had such a path and had to fall back to path emulation. - bool m_isClipped{false}; + uint32_t m_pathRectangleCount{}; + std::vector> m_clipMasks; + void ReleaseClipMasksAfterFlush(); std::shared_ptr m_cancellationSource{}; JsRuntimeScheduler m_runtimeScheduler; @@ -214,12 +217,7 @@ namespace Babylon::Polyfills::Internal void PlayPath2D(const NativeCanvasPath2D* path); void SetFilterStack(); - // Start a fresh nanovg path and drop the clip state that described the old one. - // clip() emulates a non-rectangular path by leaving it current and letting the next - // fill draw it, so any operation that resets the path invalidates that emulation: - // leaving m_isClipped set makes FillRect skip its own nvgBeginPath and append to a - // path that no longer exists, and leaving m_pathHasNonRect set makes a later clip() - // take the emulated branch on what is now a plain rect. + // The current path is independent of the saved clipping region. void ResetPathState(); struct DrawImageRectangles diff --git a/Polyfills/Canvas/Source/NativeInstanceRegistry.h b/Polyfills/Canvas/Source/NativeInstanceRegistry.h index a611b1ac3..4792b6d31 100644 --- a/Polyfills/Canvas/Source/NativeInstanceRegistry.h +++ b/Polyfills/Canvas/Source/NativeInstanceRegistry.h @@ -102,4 +102,4 @@ namespace Babylon::Polyfills::Internal return *instances; } }; -} \ No newline at end of file +} diff --git a/Polyfills/Canvas/Source/Shaders/dxbc/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/dxbc/fs_nanovg_fill.h index 4acea572a..b7544b5e8 100644 --- a/Polyfills/Canvas/Source/Shaders/dxbc/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/dxbc/fs_nanovg_fill.h @@ -1,21 +1,24 @@ -static const uint8_t fs_nanovg_fill_dxbc[3233] = +static const uint8_t fs_nanovg_fill_dxbc[3773] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x75, // FSH............u - 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x13, 0x00, 0x00, 0x00, 0x03, // _scissorMat..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, // ......u_paintMat - 0x13, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ..0........u_inn - 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol..`........ - 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x70, 0x00, 0x01, 0x00, // u_outerCol..p... - 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, // .....u_scissorEx - 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // tScale.......... - 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, // .u_extentRadius. - 0x00, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // ..........u_para - 0x6d, 0x73, 0x12, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, // ms...........u_s - 0x64, 0x66, 0x12, 0x00, 0xb0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, // df...........s_t - 0x65, 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, // ex0..........s_t - 0x65, 0x78, 0x32, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, 0x0b, 0x00, // ex20............ - 0x00, 0x44, 0x58, 0x42, 0x43, 0xe1, 0x6d, 0xb5, 0x97, 0x27, 0xb7, 0xf1, 0x7f, 0xcd, 0x81, 0xa9, // .DXBC.m..'...... - 0x2d, 0x96, 0x4d, 0x85, 0xb9, 0x01, 0x00, 0x00, 0x00, 0xb4, 0x0b, 0x00, 0x00, 0x03, 0x00, 0x00, // -.M............. + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .......u_scissor + 0x4d, 0x61, 0x74, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, // Mat...........u_ + 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, // paintMat..0..... + 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x60, // ...u_innerCol..` + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, // ........u_outerC + 0x6f, 0x6c, 0x12, 0x00, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, // ol..p........u_s + 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x00, // cissorExtScale.. + 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, // .........u_exten + 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x00, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // tRadius......... + 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x00, 0xa0, 0x00, 0x01, 0x00, // ..u_params...... + 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x12, 0x00, 0xb0, 0x00, 0x01, 0x00, // .....u_sdf...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // .....u_clipBound + 0x73, 0x12, 0x00, 0xc0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // s...........s_te + 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, // x0..........s_te + 0x78, 0x32, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x63, // x20..........s_c + 0x6c, 0x69, 0x70, 0x30, 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0xa8, 0x0d, 0x00, // lip0............ + 0x00, 0x44, 0x58, 0x42, 0x43, 0xae, 0xbe, 0x98, 0x8c, 0x53, 0x78, 0x01, 0x66, 0xe6, 0xca, 0x7b, // .DXBC....Sx.f..{ + 0xba, 0x3c, 0x70, 0x20, 0xaf, 0x01, 0x00, 0x00, 0x00, 0xa8, 0x0d, 0x00, 0x00, 0x03, 0x00, 0x00, // .

....... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, // .....8.......... + 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x02, 0x00, 0x00, // .........F...... + 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, // .8...........F.. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // .....F. ........ + 0x00, 0x12, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x08, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ........."...... + 0x00, 0x3a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, // .:. ..........@. + 0x00, 0x00, 0x00, 0x80, 0x40, 0x1f, 0x00, 0x04, 0x03, 0x1a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ....@........... + 0x00, 0x45, 0x00, 0x00, 0x8b, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, 0x00, 0xf2, 0x00, 0x10, // .E.......CU..... + 0x00, 0x02, 0x00, 0x00, 0x00, 0xe6, 0x1a, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, // .............F~. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, // ......`......E.. + 0x8b, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, 0x00, 0xf2, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, // .....CU......... + 0x00, 0x46, 0x10, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, 0x00, 0x01, 0x00, 0x00, // .F.......F~..... + 0x00, 0x00, 0x60, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0b, 0x62, 0x00, 0x10, // ..`..........b.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x8a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, // ....... ........ + 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, // ..@.........?... + 0x40, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x07, 0x72, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, // @....8...r...... + 0x00, 0xf6, 0x0f, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x02, 0x00, 0x00, // .........F...... + 0x00, 0x37, 0x00, 0x00, 0x09, 0x72, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x56, 0x05, 0x10, // .7...r.......V.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, // .....F.......F.. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xa2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x06, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x84, 0x20, 0x80, 0x41, 0x00, 0x00, // ........... .A.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x08, 0xa2, 0x00, 0x10, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x0d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x8a, 0x20, // .....V......... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0a, 0xa2, 0x00, 0x10, // .......... ..... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x0d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, // .....V........@. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........?....... + 0x3f, 0x00, 0x00, 0x00, 0x08, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, // ?............:.. + 0x80, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, // .A........@..... + 0x3f, 0x38, 0x00, 0x00, 0x07, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, // ?8...".......:.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, // .............7.. + 0x09, 0xf2, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0xa6, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x56, 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x02, 0x00, 0x00, // .V.......F...... + 0x00, 0x38, 0x00, 0x00, 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, // .8.............. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, // .....F.......8.. + 0x07, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x03, 0x00, 0x00, // .........F...... + 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x08, 0xf2, 0x00, 0x10, // .F.......8...... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x8e, 0x20, // .....F.......F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x01, 0x15, 0x00, 0x00, // ................ + 0x01, 0x15, 0x00, 0x00, 0x01, 0x15, 0x00, 0x00, 0x01, 0x15, 0x00, 0x00, 0x01, 0x31, 0x00, 0x00, // .............1.. + 0x08, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, // ..........@..... + 0x00, 0x2a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, // .*. .........9.. + 0x08, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, // .".......:. .... + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, // ......@.....@... + 0x07, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x04, 0x03, 0x0a, 0x00, 0x10, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .........2...... + 0x00, 0x46, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x80, 0x20, 0x80, 0x41, 0x00, 0x00, // .F.......F. .A.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x08, 0x32, 0x00, 0x10, // .............2.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0x8a, 0x20, // .....F......... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x0a, 0xf2, 0x00, 0x10, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, // .....F........@. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, // ............?... + 0x3f, 0x01, 0x00, 0x00, 0x0a, 0xc2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x10, // ?............... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ......@......... + 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x38, 0x00, 0x00, 0x07, 0x42, 0x00, 0x10, // ....?...?8...B.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, // .....:.......*.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x07, 0x82, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .....<.......... + 0x00, 0x2a, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x02, 0x00, 0x00, // .*.......:...... + 0x00, 0x37, 0x00, 0x00, 0x09, 0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, // .7...B.......:.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, // ......@......*.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x8b, 0xc2, 0x00, 0x00, 0x80, 0x43, 0x55, 0x15, // .....E.......CU. + 0x00, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .........F...... + 0x00, 0x36, 0x79, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, 0x00, 0x02, 0x00, 0x00, // .6y.......`..... + 0x00, 0x38, 0x00, 0x00, 0x07, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x10, // .8...........*.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, // .............8.. + 0x07, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .. ............. + 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x36, 0x00, 0x00, // .F...........6.. + 0x05, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0x00, 0x01, 0x00, 0x00, // .. ......F...... + 0x00, 0x15, 0x00, 0x00, 0x01, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0xd0, 0x00, // .....>....... }; diff --git a/Polyfills/Canvas/Source/Shaders/dxil/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/dxil/fs_nanovg_fill.h index e33d8d314..47520419c 100644 --- a/Polyfills/Canvas/Source/Shaders/dxil/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/dxil/fs_nanovg_fill.h @@ -1,23 +1,26 @@ -static const uint8_t fs_nanovg_fill_dxil[4885] = +static const uint8_t fs_nanovg_fill_dxil[5237] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x75, // FSH............u - 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x13, 0x00, 0x00, 0x00, 0x03, // _scissorMat..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, // ......u_paintMat - 0x13, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ..0........u_inn - 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol..`........ - 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x70, 0x00, 0x01, 0x00, // u_outerCol..p... - 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, // .....u_scissorEx - 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x00, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // tScale.......... - 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, // .u_extentRadius. - 0x00, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // ..........u_para - 0x6d, 0x73, 0x12, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, // ms...........u_s - 0x64, 0x66, 0x12, 0x00, 0xb0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, // df...........s_t - 0x65, 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, // ex0..........s_t - 0x65, 0x78, 0x32, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x12, 0x00, // ex20.........(.. - 0x00, 0x44, 0x58, 0x42, 0x43, 0x38, 0x8d, 0xaf, 0x1f, 0x8c, 0xb6, 0x0c, 0xd7, 0x2e, 0xaa, 0xa0, // .DXBC8.......... - 0xf5, 0x08, 0x2b, 0x65, 0xf2, 0x01, 0x00, 0x00, 0x00, 0x28, 0x12, 0x00, 0x00, 0x07, 0x00, 0x00, // ..+e.....(...... + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .......u_scissor + 0x4d, 0x61, 0x74, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, // Mat...........u_ + 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, 0x00, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, // paintMat..0..... + 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x00, 0x60, // ...u_innerCol..` + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, // ........u_outerC + 0x6f, 0x6c, 0x12, 0x00, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, // ol..p........u_s + 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x00, // cissorExtScale.. + 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, // .........u_exten + 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x00, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // tRadius......... + 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x00, 0xa0, 0x00, 0x01, 0x00, // ..u_params...... + 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x12, 0x00, 0xb0, 0x00, 0x01, 0x00, // .....u_sdf...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // .....u_clipBound + 0x73, 0x12, 0x00, 0xc0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // s...........s_te + 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, // x0..........s_te + 0x78, 0x32, 0x30, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x63, // x20..........s_c + 0x6c, 0x69, 0x70, 0x30, 0x01, 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x60, 0x13, 0x00, // lip0.........`.. + 0x00, 0x44, 0x58, 0x42, 0x43, 0x62, 0xc4, 0xd6, 0xd9, 0x35, 0xe9, 0x01, 0xb8, 0xe1, 0xaf, 0xf2, // .DXBCb...5...... + 0x24, 0xae, 0x7b, 0x11, 0xa8, 0x01, 0x00, 0x00, 0x00, 0x60, 0x13, 0x00, 0x00, 0x07, 0x00, 0x00, // $.{......`...... 0x00, 0x3c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, // .<...L.......0.. - 0x00, 0xac, 0x02, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, 0xfc, 0x02, 0x00, 0x00, 0x53, 0x46, 0x49, // .............SFI + 0x00, 0xe0, 0x02, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x53, 0x46, 0x49, // .........0...SFI 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, // 0............ISG 0x31, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1............... 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ @@ -33,276 +36,296 @@ static const uint8_t fs_nanovg_fill_dxil[4885] = 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .........(...... 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, // .@.............. 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, // .....SV_Target.. - 0x00, 0x50, 0x53, 0x56, 0x30, 0x74, 0x01, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .PSV0t...4...... + 0x00, 0x50, 0x53, 0x56, 0x30, 0xa8, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .PSV0....8...... 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x03, 0x01, 0x00, 0x00, // ................ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, // ................ - 0x00, 0x05, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x54, // .$....TEXCOORD.T - 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, // EXCOORD.TEXCOORD - 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .main........... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x03, 0x03, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, // .......D........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, 0x03, 0x02, 0x00, 0x00, 0x0a, 0x00, 0x00, // .......B........ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x62, 0x00, 0x03, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, // .......b........ - 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02, 0x42, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .......B........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .......D........ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, // .....$....TEXCOO + 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, // RD.TEXCOORD.TEXC + 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // OORD.main....... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x03, 0x03, 0x04, 0x00, // ...........D.... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, 0x03, 0x02, 0x00, // ...........B.... + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x62, 0x00, 0x03, 0x02, 0x00, // ...........b.... + 0x00, 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x02, 0x42, 0x00, 0x03, 0x02, 0x00, // ...........B.... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, // ...........D.... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, // ................ - 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x4c, 0x44, // .............ILD - 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x65, 0x39, 0x32, 0x66, 0x36, 0x63, 0x30, // N,.....$.e92f6c0 - 0x65, 0x31, 0x39, 0x32, 0x37, 0x65, 0x65, 0x34, 0x39, 0x33, 0x30, 0x64, 0x34, 0x39, 0x32, 0x65, // e1927ee4930d492e - 0x36, 0x36, 0x39, 0x32, 0x31, 0x36, 0x34, 0x63, 0x30, 0x2e, 0x70, 0x64, 0x62, 0x00, 0x00, 0x00, // 6692164c0.pdb... - 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x2f, 0x6c, // .HASH........./l - 0x0e, 0x19, 0x27, 0xee, 0x49, 0x30, 0xd4, 0x92, 0xe6, 0x69, 0x21, 0x64, 0xc0, 0x44, 0x58, 0x49, // ..'.I0...i!d.DXI - 0x4c, 0x24, 0x0f, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xc9, 0x03, 0x00, 0x00, 0x44, 0x58, 0x49, // L$...`.......DXI - 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0x42, 0x43, 0xc0, // L............BC. - 0xde, 0x21, 0x0c, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, // .!......... .... - 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, // .......#.A..I..2 - 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62, 0x80, 0x18, 0x45, // 9....%......b..E - 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, 0x4b, 0x0a, 0x32, 0x62, // .B..B..2.8..K.2b - 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, 0x42, 0xe4, 0x48, 0x0e, // .H.. CF....2B.H. - 0x90, 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, 0x8a, 0x04, 0x31, 0x46, // ..#.PAQ.......1F - 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, 0xff, 0xff, 0xff, 0xff, // .Q.............. - 0x07, 0x40, 0x02, 0xa8, 0x0d, 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, 0x20, 0x6d, 0x30, 0x86, // .@.......... m0. - 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, 0x00, 0x03, 0x00, 0x00, // .........I...... - 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, 0x89, 0x20, 0x00, // ...`B L....... . - 0x00, 0x5b, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, 0x04, 0x13, 0x23, 0xa4, // .[...2".. d...#. - 0x84, 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, 0x8c, 0x0b, 0x84, 0xc4, // ...#......L..... - 0x4c, 0x10, 0xa0, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, 0x66, 0x00, 0xe6, 0x08, 0xc0, 0x60, 0x8e, // L....@...f....`. - 0x00, 0x29, 0xc6, 0x40, 0x10, 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, 0x88, 0x62, 0x20, 0xa4, // .).@.DA.Q.. .b . - 0x10, 0x03, 0x31, 0x0c, 0xa4, 0x94, 0x61, 0x20, 0x06, 0x62, 0x6e, 0x1a, 0x2e, 0x7f, 0xc2, 0x1e, // ..1...a .bn..... - 0x42, 0xf2, 0x57, 0x42, 0x5a, 0x89, 0xc9, 0x2f, 0x6e, 0x1b, 0x15, 0xc3, 0x30, 0x0c, 0x04, 0x15, // B.WBZ../n...0... - 0xf7, 0x0c, 0x97, 0x3f, 0x61, 0x0f, 0x21, 0xf9, 0x21, 0xd0, 0x0c, 0x0b, 0x81, 0x82, 0xa7, 0x30, // ...?a.!.!......0 - 0x0e, 0x01, 0x41, 0xc3, 0x30, 0x0c, 0x04, 0x41, 0x0c, 0x14, 0x1d, 0x35, 0x5c, 0xfe, 0x84, 0x3d, // ..A.0..A...5...= - 0x84, 0xe4, 0x73, 0x1b, 0x55, 0xac, 0xc4, 0xe4, 0x17, 0xb7, 0x8d, 0x88, 0x61, 0x18, 0x86, 0x42, // ..s.U.......a..B - 0x4c, 0x04, 0x44, 0x10, 0x55, 0x8a, 0x81, 0x18, 0x86, 0x81, 0xac, 0x39, 0x82, 0xa0, 0x18, 0x10, // L.D.U......9.... - 0x51, 0x10, 0x04, 0x46, 0xd9, 0x40, 0xc0, 0x30, 0x02, 0x31, 0xcc, 0xd4, 0x06, 0xe3, 0xc0, 0x0e, // Q..F.@.0.1...... - 0xe1, 0x30, 0x0f, 0xf3, 0xe0, 0x06, 0xb4, 0x50, 0x0e, 0xf8, 0x40, 0x0f, 0xf5, 0x20, 0x0f, 0xe5, // .0.....P..@.. .. - 0x20, 0x07, 0xa4, 0xc0, 0x07, 0xf6, 0x50, 0x0e, 0xe3, 0x40, 0x0f, 0xef, 0x20, 0x0f, 0x7c, 0x60, // .....P..@.. .|` - 0x0e, 0xec, 0xf0, 0x0e, 0xe1, 0x40, 0x0f, 0x6c, 0x00, 0x06, 0x74, 0xe0, 0x07, 0x60, 0xe0, 0x07, // .....@.l..t..`.. - 0x7a, 0xa0, 0x07, 0xed, 0x90, 0x0e, 0xf0, 0x30, 0x0f, 0xbf, 0x40, 0x0f, 0xf9, 0x00, 0x0f, 0xe5, // z......0..@..... - 0x80, 0x02, 0x62, 0x26, 0x31, 0x18, 0x07, 0x76, 0x08, 0x87, 0x79, 0x98, 0x07, 0x37, 0xa0, 0x85, // ..b&1..v..y..7.. - 0x72, 0xc0, 0x07, 0x7a, 0xa8, 0x07, 0x79, 0x28, 0x07, 0x39, 0x20, 0x05, 0x3e, 0xb0, 0x87, 0x72, // r..z..y(.9 .>..r - 0x18, 0x07, 0x7a, 0x78, 0x07, 0x79, 0xe0, 0x03, 0x73, 0x60, 0x87, 0x77, 0x08, 0x07, 0x7a, 0x60, // ..zx.y..s`.w..z` - 0x03, 0x30, 0xa0, 0x03, 0x3f, 0x00, 0x03, 0x3f, 0x40, 0x02, 0xd7, 0x91, 0x37, 0x8c, 0x30, 0x0c, // .0..?..?@...7.0. - 0x39, 0x00, 0xce, 0x34, 0xa1, 0xc3, 0x3b, 0xcc, 0x03, 0x3d, 0xb0, 0x43, 0x38, 0xe4, 0xc3, 0x3b, // 9..4..;..=.C8..; - 0xd4, 0x03, 0x3d, 0xb8, 0x01, 0x19, 0x9c, 0x02, 0x3b, 0xbc, 0x83, 0x38, 0x84, 0x03, 0x3b, 0xcc, // ..=.....;..8..;. - 0x03, 0x42, 0xc2, 0x90, 0xe3, 0x38, 0x8e, 0xe3, 0x48, 0xbc, 0x49, 0x9a, 0x22, 0x4a, 0x98, 0x7c, // .B...8..H.I."J.| - 0x16, 0x60, 0x9e, 0x85, 0x88, 0xd8, 0x09, 0x98, 0x08, 0x14, 0x10, 0x44, 0x26, 0x03, 0x31, 0x05, // .`.........D&.1. - 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, // ...r..t`.6h.yh.r - 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, // ....P.m..zP.m..z - 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, // 0.r..s .m..q..s - 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, // .m..x..s .m..q`. - 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, // z0.r...0.r..s .m - 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, // ..v@.z`.t.....v. - 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, 0x20, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, // .s .m`.s .z0.r.. - 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, // .`.t..v@.m..x..q - 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x76, 0x40, 0x07, 0x43, 0x9e, 0x00, 0x00, 0x00, // `.z0.r..v@.C.... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, // .........<...... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x10, 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // ......y. ....... - 0x00, 0x00, 0x18, 0xf2, 0x28, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, // ....(@.........0 - 0xe4, 0x61, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xc8, 0x13, 0x01, // .a..........`... - 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x90, 0x87, 0x02, 0x02, 0x60, 0x00, // .0............`. - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, 0x8f, 0x05, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, // .......!...@.... - 0x00, 0x00, 0x00, 0x00, 0x43, 0x9e, 0x0c, 0x08, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....C........... - 0x00, 0xb2, 0x40, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, // ..@......2.....L - 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x4a, 0x60, 0x04, 0xa0, 0x24, 0x8a, 0xa1, // ...&G..C"J`..$.. - 0x08, 0x0a, 0x70, 0xa0, 0x0c, 0xca, 0x83, 0x8a, 0x92, 0x28, 0x83, 0x42, 0x18, 0x01, 0x28, 0x82, // ..p......(.B..(. - 0x02, 0xa1, 0x6f, 0x06, 0x80, 0xc6, 0x19, 0x00, 0x2a, 0x67, 0x00, 0xc8, 0x1c, 0x8b, 0x51, 0x08, // ..o.....*g....Q. - 0x00, 0x00, 0x80, 0xe7, 0x79, 0x9e, 0xe7, 0x01, 0x00, 0x79, 0x18, 0x00, 0x00, 0x71, 0x00, 0x00, // ....y....y...q.. - 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0x44, 0x8f, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, // ...L.F..D..o..M. - 0x24, 0xc6, 0x05, 0xc7, 0x45, 0xa6, 0x06, 0xa6, 0xc6, 0x65, 0x46, 0x06, 0x04, 0x45, 0x2c, 0x06, // $...E....eF..E,. - 0xc6, 0x26, 0x2c, 0xc7, 0x26, 0x86, 0x2c, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x88, 0x64, 0x82, 0x40, // .&,.&.,eC.L..d.@ - 0x28, 0x1b, 0x84, 0x81, 0x98, 0x20, 0x10, 0xcb, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, 0x4d, 0x10, // (.... ...a0(..M. - 0x08, 0x66, 0xc3, 0x80, 0x24, 0xc4, 0x04, 0xe1, 0xbb, 0x08, 0x4c, 0x10, 0x88, 0x66, 0x83, 0x40, // .f..$.....L..f.@ - 0x18, 0x1b, 0x12, 0x62, 0x61, 0x08, 0x62, 0x68, 0x08, 0x67, 0x43, 0x32, 0x2c, 0x0c, 0x31, 0x0c, // ...ba.bh.gC2,.1. - 0x0d, 0xe1, 0x6c, 0x10, 0x1e, 0x68, 0x82, 0x30, 0x06, 0xd8, 0x04, 0x81, 0x70, 0x36, 0x20, 0x84, // ..l..h.0....p6 . - 0xc4, 0x10, 0xc4, 0x30, 0x01, 0x1b, 0x02, 0x6a, 0x82, 0x50, 0x06, 0xd9, 0x06, 0x84, 0xb0, 0x18, // ...0...j.P...... - 0x82, 0x18, 0x08, 0x60, 0x03, 0x32, 0x58, 0x0c, 0x31, 0x0c, 0x04, 0xb0, 0x41, 0xb8, 0xb0, 0x0d, // ...`.2X.1...A... - 0x44, 0x04, 0x54, 0xd9, 0x04, 0xc1, 0x0c, 0xb4, 0x0d, 0xc1, 0x36, 0x41, 0x10, 0x00, 0x12, 0x6d, // D.T.......6A...m - 0x61, 0x69, 0x6e, 0x5c, 0xa6, 0xac, 0xbe, 0xa0, 0xde, 0xe6, 0xd2, 0xe8, 0xd2, 0xde, 0xdc, 0x26, // ain............& - 0x08, 0x45, 0x34, 0x41, 0x28, 0xa4, 0x0d, 0x01, 0x31, 0x41, 0x28, 0xa6, 0x09, 0x42, 0x41, 0x6d, // .E4A(...1A(..BAm - 0x58, 0x88, 0x0f, 0x0c, 0xc2, 0x40, 0x0c, 0xc6, 0x60, 0x18, 0x03, 0x82, 0x0c, 0x00, 0x22, 0x54, // X....@..`....."T - 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x13, 0x84, 0xa2, 0x9a, 0x20, 0x10, 0xcf, 0x06, 0x01, // EXCOORD.... .... - 0x0d, 0xd0, 0x60, 0xc3, 0x32, 0x98, 0x01, 0x18, 0x90, 0x81, 0x18, 0x9c, 0xc1, 0x70, 0x06, 0x03, // ..`.2........p.. - 0x19, 0xa4, 0xc1, 0x86, 0x60, 0xd8, 0xb0, 0x34, 0x66, 0x00, 0x06, 0x64, 0xb0, 0x06, 0x67, 0x30, // ....`..4f..d..g0 - 0x9c, 0xc1, 0x70, 0x06, 0x69, 0xb0, 0x21, 0x68, 0x36, 0x2c, 0x68, 0x60, 0x06, 0x60, 0x40, 0x06, // ..p.i.!h6,h`.`@. - 0x6d, 0x70, 0x06, 0xc3, 0x19, 0x34, 0x64, 0x90, 0x06, 0x1b, 0x88, 0x32, 0x50, 0x03, 0x36, 0x70, // mp...4d....2P.6p - 0x03, 0x26, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x13, 0x84, 0xc2, 0x9a, 0x20, // .&SV_Target.... - 0x10, 0xd0, 0x06, 0x01, 0x0d, 0xe4, 0x60, 0xc3, 0x42, 0xc0, 0x01, 0x18, 0xc4, 0x81, 0x18, 0x90, // ......`.B....... - 0xc1, 0x30, 0x06, 0x04, 0x19, 0xcc, 0xc1, 0x86, 0x80, 0x0e, 0x36, 0x0c, 0x6f, 0x50, 0x07, 0xc0, // .0........6.oP.. - 0x86, 0xa2, 0xf3, 0xec, 0x40, 0x03, 0xaa, 0xb0, 0xb1, 0xd9, 0xb5, 0xb9, 0xa4, 0x91, 0x95, 0xb9, // ....@........... - 0xd1, 0x4d, 0x09, 0x82, 0x2a, 0x64, 0x78, 0x2e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x53, // .M..*dx.versionS - 0x02, 0xa2, 0x09, 0x19, 0x9e, 0x8b, 0x5d, 0x18, 0x9b, 0x5d, 0x99, 0xdc, 0x94, 0xc0, 0xa8, 0x43, // ......]..].....C - 0x86, 0xe7, 0x32, 0x87, 0x16, 0x46, 0x56, 0x26, 0xd7, 0xf4, 0x46, 0x56, 0xc6, 0x36, 0x25, 0x48, // ..2..FV&..FV.6%H - 0xca, 0x90, 0xe1, 0xb9, 0xc8, 0x95, 0xcd, 0xbd, 0xd5, 0xc9, 0x8d, 0x95, 0xcd, 0x4d, 0x09, 0xb2, // .............M.. - 0x3a, 0x64, 0x78, 0x2e, 0x76, 0x69, 0x65, 0x77, 0x49, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, // :dx.viewIdStateS - 0x82, 0xad, 0x0e, 0x19, 0x9e, 0x4b, 0x99, 0x1b, 0x9d, 0x5c, 0x1e, 0xd4, 0x5b, 0x9a, 0x1b, 0xdd, // .....K......[... - 0xdc, 0x94, 0xc0, 0x0e, 0x00, 0x79, 0x18, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, // .....y...Q...3.. - 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, // ....f..=.C8...B. - 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, // .yx.s.q......... - 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, // .3.B.......f0.=. - 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, // C8.....=.C=..=.x - 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, // .tp.{..yH.pp.zp. - 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, // vx.p .........0. - 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, // n0.....P.3....!. - 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, // .!..a.f0.;..;.C9 - 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, // ..<..<..;...v`.{ - 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, // h.7h.rh.7..p..p` - 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, // .v(.v..vx.w.._.. - 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, // q..r..y..,...... - 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, // ....0.b......... - 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, // ....a..!.....a.. - 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, // .C9.C9.C9.C9..8. - 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, // C8..;../..<..;.. - 0x3b, 0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87, // ;....!.|p.z(.v.. - 0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10, 0x0e, // ..C..... ....... - 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, 0x0f, 0xf4, 0x30, 0x83, 0x81, 0xc8, 0x01, 0x1f, // .......P..0..... - 0xdc, 0x40, 0x1c, 0xe4, 0xa1, 0x1c, 0xc2, 0x61, 0x1d, 0xdc, 0x40, 0x1c, 0xe4, 0x01, 0x00, 0x00, // .@.....a..@..... - 0x00, 0x71, 0x20, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x36, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0x7e, // .q ..#...6....<~ - 0x80, 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x9b, 0xc1, 0x36, 0x5c, 0xbe, 0xf3, 0xf8, 0x42, 0x40, // .4@...m..6....B@ - 0x15, 0x05, 0x11, 0x95, 0x0e, 0x30, 0x94, 0x84, 0x01, 0x08, 0x98, 0x5f, 0xdc, 0xb6, 0x21, 0x48, // .....0....._..!H - 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x44, 0x04, 0x30, 0x11, 0x21, 0xd0, 0x0c, 0x0b, 0x61, 0x01, 0xd3, // ..;./D.0.!...a.. - 0x70, 0xf9, 0xce, 0xe3, 0x2f, 0x0e, 0x30, 0x88, 0xcd, 0x43, 0x4d, 0x7e, 0x71, 0xdb, 0x56, 0x00, // p.../.0..CM~q.V. - 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x04, 0x30, 0xcf, 0x42, 0xf8, 0xc5, 0x6d, 0x9b, 0x40, 0x35, 0x5c, // ...<..0.B..m.@5. - 0xbe, 0xf3, 0xf8, 0xd2, 0xe4, 0x44, 0x04, 0x4a, 0x4d, 0x0f, 0x35, 0xf9, 0xc5, 0x6d, 0xdb, 0x81, // .....D.JM.5..m.. - 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x13, 0x11, 0x4d, 0x08, 0x10, 0x61, 0x7e, 0x71, 0xdb, 0x46, 0xf0, // 4......M..a~q.F. - 0x0c, 0x97, 0xef, 0x3c, 0x3e, 0xd5, 0x00, 0x11, 0xe6, 0x17, 0xb7, 0x6d, 0x00, 0x04, 0x03, 0x20, // ...<>......m... - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x13, 0x04, 0x54, // .....a ........T - 0x2c, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x84, 0x8d, 0x00, 0x50, 0x51, 0x02, 0x44, // ,...........PQ.D - 0x94, 0x43, 0xc1, 0x14, 0x45, 0x41, 0x94, 0x45, 0xe1, 0x95, 0xdd, 0x0c, 0x40, 0x19, 0x15, 0x52, // .C..EA.E....@..R - 0x21, 0x14, 0x57, 0x29, 0x94, 0x1c, 0x0d, 0x63, 0x04, 0x20, 0x08, 0x82, 0x28, 0x18, 0x8c, 0x11, // !.W)...c. ..(... - 0x80, 0x20, 0x08, 0x92, 0x60, 0x30, 0x46, 0x00, 0x82, 0x20, 0x88, 0xff, 0xc2, 0x18, 0x01, 0x08, // . ..`0F.. ...... - 0x82, 0x20, 0x08, 0x06, 0x63, 0x04, 0x20, 0x08, 0x82, 0xf0, 0x37, 0x02, 0x30, 0x03, 0x30, 0x46, // . ..c. ...7.0.0F - 0x00, 0x82, 0x20, 0x88, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, // .. ......#....`. - 0xad, 0x41, 0xf2, 0xa9, 0x81, 0x1a, 0x60, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0xb1, 0x81, // .A....`#....`... - 0x02, 0x06, 0x6a, 0xa0, 0x06, 0xd9, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x64, 0x6d, 0xb0, 0x8c, // ..j....A.. .dm.. - 0x01, 0x1b, 0xb0, 0x81, 0x36, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x99, 0x1b, 0x30, 0x64, 0xc0, // ....6b.. ....0d. - 0x06, 0x6c, 0xb0, 0x8d, 0x18, 0x24, 0x00, 0x08, 0x82, 0x41, 0xf6, 0x06, 0x8d, 0x18, 0xb4, 0x41, // .l...$...A.....A - 0x1b, 0x70, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0xdc, 0x41, 0x94, 0x06, 0x6e, 0x40, 0x06, // .p#....``.A..n@. - 0xd4, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x78, 0x20, 0xa9, 0xc1, 0x1b, 0x74, 0xd5, 0x88, // ..A.. ..x ...t.. - 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x79, 0x30, 0xb5, 0x01, 0x1c, 0x98, 0x81, 0x35, 0x62, 0x90, // A.. ..y0.....5b. - 0x00, 0x20, 0x08, 0x06, 0x86, 0x1e, 0x50, 0x6e, 0x10, 0x07, 0xdf, 0x35, 0x62, 0x90, 0x00, 0x20, // . ....Pn...5b.. - 0x08, 0x06, 0xc6, 0x1e, 0x54, 0x73, 0x20, 0x07, 0x68, 0x80, 0x8d, 0x18, 0x24, 0x00, 0x08, 0x82, // ....Ts .h...$... - 0x81, 0xc1, 0x07, 0x16, 0x1d, 0xcc, 0x41, 0x18, 0x64, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0x40, // ......A.d#....`@ - 0xe1, 0xc1, 0x76, 0xd0, 0xc1, 0x68, 0x42, 0x00, 0x8c, 0x26, 0x08, 0xc1, 0x88, 0xc1, 0x01, 0x80, // ..v..hB..&...... - 0x20, 0x18, 0x50, 0x7b, 0xe0, 0x29, 0x78, 0x30, 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x62, // .P{.)x0.....B0b - 0x70, 0x00, 0x20, 0x08, 0x06, 0x94, 0x1f, 0x84, 0x41, 0x73, 0x07, 0xa3, 0x09, 0x01, 0x30, 0x9a, // p. .....As....0. - 0x20, 0x04, 0x86, 0x2c, 0xf2, 0x19, 0x31, 0x40, 0x00, 0x10, 0x04, 0x03, 0x2b, 0x14, 0xc0, 0xc0, // ..,..1@....+... - 0x58, 0x02, 0x23, 0x02, 0xe8, 0x98, 0xe2, 0xc8, 0x67, 0xc4, 0x00, 0x01, 0x40, 0x10, 0x0c, 0x2c, // X.#.....g...@.., - 0x52, 0x18, 0x03, 0xc4, 0x09, 0x2c, 0x30, 0xa0, 0x33, 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0x4c, // R....,0.3b`. ..L - 0x2a, 0x84, 0x02, 0x31, 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0x8c, 0x2a, 0x88, 0x82, 0x30, 0x62, // *..1b`. ...*..0b - 0x70, 0x00, 0x20, 0x08, 0x06, 0x54, 0x2a, 0xb0, 0x01, 0xf6, 0x06, 0xa3, 0x09, 0x01, 0x30, 0x9a, // p. ..T*.......0. - 0x20, 0x04, 0x56, 0x08, 0xf1, 0xb1, 0x42, 0x88, 0xcf, 0x68, 0x42, 0x21, 0x8c, 0x26, 0x18, 0x83, // .V...B..hB!.&.. - 0x11, 0x82, 0x7c, 0x8c, 0x10, 0xe4, 0x63, 0x69, 0x20, 0xc4, 0xc7, 0xd4, 0x40, 0x88, 0xcf, 0x88, // ..|...ci ...@... - 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0xb7, 0x50, 0x07, 0x82, 0x1a, 0x8c, 0x18, 0x1c, 0x00, 0x08, // ... .(.P........ - 0x82, 0x81, 0x82, 0x0b, 0x75, 0x10, 0xa4, 0xc1, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0xb9, // ....u....... .(. - 0x70, 0x07, 0x03, 0x1b, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0xa2, 0x0b, 0x77, 0x10, 0xac, // p............w.. - 0x81, 0x05, 0x83, 0x7c, 0xac, 0x0c, 0xe2, 0x40, 0x3e, 0x16, 0xcc, 0x01, 0x7c, 0x46, 0x0c, 0x0c, // ...|...@>...|F.. - 0x00, 0x04, 0xc1, 0x80, 0xe9, 0x85, 0x5a, 0x08, 0x0c, 0x0e, 0x82, 0xf8, 0x8c, 0x18, 0x1c, 0x00, // ......Z......... - 0x08, 0x82, 0x01, 0xd5, 0x0b, 0xa0, 0xc0, 0x06, 0xa4, 0x30, 0x9a, 0x10, 0x04, 0x16, 0x0c, 0xf2, // .........0...... - 0x19, 0x31, 0x38, 0x00, 0x10, 0x04, 0x03, 0x65, 0x1c, 0x40, 0x81, 0x0e, 0x82, 0x11, 0x83, 0x03, // .18....e.@...... - 0x00, 0x41, 0x30, 0x50, 0xc8, 0x21, 0x14, 0xea, 0x80, 0x0d, 0x2c, 0x10, 0xe4, 0x33, 0x9a, 0x60, // .A0P.!....,..3.` - 0x0c, 0xc3, 0x11, 0x81, 0x1e, 0x04, 0xdf, 0x2c, 0x43, 0x20, 0x04, 0x23, 0x06, 0x07, 0x00, 0x82, // .......,C .#.... - 0x60, 0x40, 0x95, 0x03, 0x2a, 0xd0, 0x01, 0x2f, 0x8c, 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x8c, // `@..*../.&..h... - 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0x85, 0x0e, 0xab, 0x70, 0x07, 0xa7, 0x30, 0x9a, 0x10, 0x00, // .........p..0... - 0xa3, 0x09, 0x42, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0xd4, 0x3a, 0xb8, 0x82, 0x1e, 0xa0, // ..B0bp. ...:.... - 0xc2, 0x68, 0x42, 0x00, 0x8c, 0x26, 0x08, 0x81, 0x21, 0x78, 0x20, 0x9f, 0x11, 0x03, 0x04, 0x00, // .hB..&..!x ..... - 0x41, 0x30, 0xb0, 0xdc, 0xa1, 0x15, 0x0c, 0x3c, 0x08, 0x8c, 0x08, 0xa0, 0x63, 0xca, 0x1e, 0xc8, // A0.....<....c... - 0x67, 0xc4, 0x00, 0x01, 0x40, 0x10, 0x0c, 0xac, 0x78, 0x80, 0x05, 0x64, 0x0f, 0x02, 0x0b, 0x0c, // g...@...x..d.... - 0xe8, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0x45, 0x0f, 0xb7, 0x30, 0x0a, 0xef, 0x30, 0x9a, // ........E..0..0. - 0x10, 0x08, 0xa3, 0x09, 0x02, 0x30, 0x9a, 0x30, 0x04, 0x26, 0x0c, 0xf1, 0x31, 0x81, 0x88, 0xcf, // .....0.0.&..1... - 0x88, 0x81, 0x01, 0x80, 0x20, 0x18, 0x30, 0xfc, 0x40, 0x0f, 0xca, 0x88, 0x81, 0x01, 0x80, 0x20, // .... .0.@...... - 0x18, 0x30, 0xfd, 0x50, 0x0f, 0x88, 0x09, 0x44, 0x7c, 0x4c, 0x20, 0xe2, 0x33, 0x62, 0x70, 0x00, // .0.P...D|L .3bp. - 0x20, 0x08, 0x06, 0x4a, 0x48, 0xfc, 0x82, 0x10, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0x22, // ..JH.........." - 0x12, 0xbf, 0x10, 0xd4, 0xc2, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0x23, 0x11, 0x0e, 0x84, // ......... .(#... - 0x2d, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0x42, 0x12, 0xe2, 0x40, 0xdc, 0x82, 0x09, 0x82, // -.......B..@.... - 0x7c, 0x4c, 0x10, 0xe4, 0x63, 0x81, 0x00, 0x9f, 0x11, 0x03, 0x03, 0x00, 0x41, 0x30, 0x60, 0x4e, // |L..c.......A0`N - 0xa2, 0x1d, 0x82, 0xd1, 0x84, 0x34, 0x00, 0x2c, 0xf0, 0x05, 0xf9, 0x58, 0x30, 0xc5, 0xc7, 0x02, // .....4.,...X0... - 0x05, 0x3e, 0x16, 0x14, 0xf0, 0xb1, 0xa0, 0xa0, 0xcf, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, // .>........... .( - 0x2f, 0xd1, 0x0e, 0x81, 0x38, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0x02, 0x13, 0xed, 0x10, // /...8........... - 0x84, 0xc3, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x50, 0x2e, 0x11, 0x0f, 0xbd, 0x70, 0x0f, 0xa3, // ...... .P....p.. - 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0xa3, 0x09, 0x83, 0x30, 0x9a, 0x40, 0x0c, 0x23, 0x06, 0x07, // ..0. ....0.@.#.. - 0x00, 0x82, 0x60, 0x40, 0xcd, 0x84, 0x3d, 0x88, 0x43, 0x4b, 0x8c, 0x26, 0x04, 0xc0, 0x68, 0x82, // ..`@..=.CK.&..h. - 0x10, 0x8c, 0x26, 0x0c, 0xc2, 0x68, 0x02, 0x31, 0x58, 0x42, 0xc4, 0xc7, 0x12, 0x22, 0x3e, 0x96, // ..&..h.1XB...">. - 0x10, 0xf1, 0xb1, 0x84, 0x88, 0x8f, 0x11, 0x8f, 0x7c, 0x8c, 0x80, 0xe4, 0x63, 0x44, 0x24, 0x1f, // ........|...cD$. - 0x23, 0x24, 0xf9, 0x18, 0xc1, 0xc0, 0xc7, 0x08, 0x06, 0x3e, 0x46, 0x30, 0xf0, 0x31, 0x82, 0x81, // #$.......>F0.1.. - 0x8f, 0x89, 0x02, 0x2b, 0xc8, 0xc7, 0x8a, 0x40, 0x3e, 0x56, 0x08, 0xf2, 0xb1, 0x62, 0x90, 0x8f, // ...+...@>V...b.. - 0x15, 0x84, 0x7c, 0x66, 0x09, 0xa6, 0xe1, 0x08, 0x53, 0xe8, 0x87, 0xe0, 0x9b, 0x65, 0x18, 0x8c, // ..|f....S....e.. - 0x60, 0xc4, 0xe0, 0x00, 0x40, 0x10, 0x0c, 0x28, 0xb5, 0x68, 0x89, 0x7c, 0x08, 0x8b, 0xd1, 0x84, // `...@..(.h.|.... - 0x00, 0x18, 0x4d, 0x10, 0x82, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0xa0, 0xda, 0x02, 0x26, 0xf8, // ..M......A0...&. - 0x81, 0x25, 0x46, 0x13, 0x02, 0x60, 0x34, 0x41, 0x08, 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, 0x80, // .%F..`4A.F...... - 0x82, 0x8b, 0x99, 0xf8, 0x87, 0x96, 0x18, 0x4d, 0x08, 0x80, 0xd1, 0x04, 0x21, 0x30, 0xa4, 0x1f, // .......M....!0.. - 0xe4, 0x33, 0x62, 0x80, 0x00, 0x20, 0x08, 0x06, 0xd6, 0x5c, 0xc8, 0x84, 0xd1, 0x0f, 0x81, 0x11, // .3b.. .......... - 0x01, 0x74, 0x4c, 0x01, 0x09, 0xf9, 0x8c, 0x18, 0x20, 0x00, 0x08, 0x82, 0x81, 0x65, 0x17, 0x35, // .tL..... ....e.5 - 0x81, 0x80, 0x44, 0x60, 0x81, 0x01, 0x9d, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0xa0, 0xf2, 0x82, // ..D`.......A0... - 0x27, 0x50, 0x82, 0x2e, 0x46, 0x13, 0x02, 0x60, 0x34, 0x41, 0x08, 0xec, 0x10, 0xe8, 0x63, 0x85, // 'P..F..`4A....c. - 0x40, 0x9f, 0x11, 0x83, 0x07, 0x00, 0x41, 0x30, 0x88, 0xfe, 0x42, 0x2c, 0x60, 0xc2, 0x25, 0x84, // @.....A0..B,`.%. - 0x60, 0x26, 0x66, 0x42, 0x2f, 0xf4, 0x02, 0x2c, 0x66, 0x62, 0x34, 0x21, 0x00, 0x46, 0x13, 0x84, // `&fB/..,fb4!.F.. - 0x60, 0x34, 0x61, 0x10, 0x46, 0x13, 0x88, 0x61, 0x34, 0xc1, 0x1c, 0x84, 0xe1, 0x88, 0x00, 0x27, // `4a.F..a4......' - 0x82, 0x6f, 0x96, 0x81, 0x28, 0x02, 0x33, 0x06, 0xf9, 0x98, 0x41, 0xc8, 0xc7, 0x8c, 0x42, 0x3e, // .o..(.3...A...B> - 0xb3, 0x04, 0xc5, 0x40, 0xc5, 0x60, 0x10, 0xd2, 0x30, 0x50, 0x31, 0x18, 0x84, 0x34, 0x0c, 0x54, // ...@.`..0P1..4.T - 0x0c, 0x06, 0x21, 0x0d, 0xc3, 0x11, 0xc8, 0x58, 0x04, 0xdf, 0x74, 0x03, 0x31, 0x04, 0xd3, 0x0d, // ..!....X..t.1... - 0xc5, 0x20, 0x4c, 0x37, 0x18, 0xcc, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0xd4, 0x6b, 0xc8, // . L7..0bp. ...k. - 0x85, 0x4f, 0xa4, 0xc6, 0x68, 0x42, 0x00, 0x8c, 0x26, 0x08, 0xc1, 0x68, 0xc2, 0x20, 0x8c, 0x26, // .O..hB..&..h. .& - 0x10, 0x83, 0xc9, 0x03, 0x3f, 0xc8, 0xc7, 0x9a, 0x40, 0x3e, 0x16, 0x18, 0xf2, 0xb1, 0x65, 0x90, // ....?...@>....e. - 0x8f, 0x05, 0x87, 0x7c, 0x8c, 0x29, 0xe4, 0x63, 0x01, 0x22, 0x1f, 0x6b, 0x0e, 0xf9, 0x58, 0x90, // ...|.).c.".k..X. - 0xc8, 0x67, 0x96, 0x60, 0x1a, 0x8e, 0xd0, 0x87, 0xba, 0x08, 0xbe, 0x59, 0x86, 0xe9, 0x08, 0x86, // .g.`.......Y.... - 0x23, 0xf6, 0x01, 0x2f, 0x82, 0x6f, 0x96, 0x01, 0x69, 0x82, 0x11, 0x83, 0x07, 0x00, 0x41, 0x30, // #../.o..i.....A0 - 0x88, 0xc0, 0x63, 0x34, 0xe2, 0xe2, 0x2d, 0xd6, 0x42, 0x2d, 0xe8, 0x82, 0x2e, 0x76, 0x63, 0x37, // ..c4..-.B-...vc7 - 0x42, 0x83, 0x2e, 0x46, 0x13, 0x02, 0x60, 0x34, 0x41, 0x08, 0x46, 0x13, 0x06, 0x61, 0x34, 0x81, // B..F..`4A.F..a4. - 0x18, 0x46, 0x13, 0x4e, 0x42, 0x18, 0x8e, 0x08, 0xf2, 0x22, 0xf8, 0x66, 0x19, 0x12, 0x25, 0x30, // .F.NB....".f..%0 - 0x63, 0x90, 0x8f, 0x19, 0x84, 0x7c, 0xcc, 0x28, 0xe4, 0x33, 0x4b, 0xa0, 0x0c, 0x54, 0x0c, 0x46, // c....|.(.3K..T.F - 0x22, 0x21, 0x03, 0x15, 0x83, 0x91, 0x48, 0xc8, 0x40, 0xc5, 0x60, 0x24, 0x12, 0x32, 0x1c, 0x81, // "!....H.@.`$.2.. - 0x90, 0x46, 0xf0, 0xcd, 0x32, 0x2c, 0x4c, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x54, 0x7b, // .F..2,L0bp. ..T{ - 0xc0, 0x06, 0x5f, 0xc8, 0xc6, 0x68, 0x42, 0x00, 0x98, 0x11, 0xc4, 0x67, 0x34, 0x61, 0x10, 0x4c, // .._..hB....g4a.L - 0x08, 0xe8, 0x63, 0x41, 0x6a, 0xc0, 0x67, 0xc4, 0xe0, 0x00, 0x40, 0x10, 0x0c, 0x14, 0xfb, 0xa0, // ..cAj.g...@..... - 0x8d, 0x20, 0x35, 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, 0x40, 0xb9, 0x0f, 0xda, 0x08, 0x50, 0x63, // . 5F.....@....Pc - 0x34, 0x01, 0x09, 0xac, 0x09, 0xe2, 0x63, 0xc1, 0x41, 0x1f, 0x0b, 0x5e, 0x03, 0x3e, 0x23, 0x06, // 4.....c.A..^.>#. - 0x07, 0x00, 0x82, 0x60, 0xa0, 0xf0, 0x87, 0x6e, 0x04, 0xaf, 0x31, 0x62, 0x70, 0x00, 0x20, 0x08, // ...`...n..1bp. . - 0x06, 0x4a, 0x7f, 0xe8, 0x46, 0xe0, 0x1a, 0xf6, 0x1a, 0x41, 0x7c, 0x2c, 0x40, 0xe4, 0x33, 0x4b, // .J..F....A|,@.3K - 0xc0, 0x0c, 0x54, 0x0c, 0xc2, 0x82, 0x06, 0xca, 0x40, 0xc5, 0x40, 0x2c, 0x68, 0xa0, 0x0c, 0x54, // ..T.....@.@,h..T - 0x0c, 0xc6, 0x82, 0x06, 0xca, 0x40, 0xc5, 0x80, 0x2c, 0x7a, 0xa0, 0x18, 0x91, 0x16, 0xf2, 0x31, // .....@..,z.....1 - 0x42, 0x2d, 0xe4, 0x63, 0xc4, 0x5a, 0xc8, 0xc7, 0x08, 0xb6, 0x90, 0xcf, 0x88, 0xc1, 0x01, 0x80, // B-.c.Z.......... - 0x20, 0x18, 0x50, 0x25, 0x82, 0x1e, 0xb4, 0xf1, 0x1f, 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, // .P%........0. . - 0xa3, 0x09, 0x83, 0x30, 0x9a, 0x40, 0x0c, 0x96, 0x10, 0xf2, 0xb1, 0x84, 0x90, 0x8f, 0x25, 0x84, // ...0.@........%. - 0x7c, 0x2c, 0x21, 0xe4, 0x33, 0x4b, 0x30, 0x0d, 0x47, 0xac, 0x45, 0x7a, 0x04, 0xdf, 0x2c, 0x83, // |,!.3K0.G.Ez..,. - 0x33, 0x05, 0x23, 0x06, 0x0f, 0x00, 0x82, 0x60, 0x10, 0xc1, 0xc8, 0x7c, 0x84, 0xc7, 0x6f, 0xec, // 3.#....`...|..o. - 0x86, 0x6e, 0x90, 0x07, 0x79, 0xac, 0xc8, 0x8a, 0xc4, 0x07, 0x79, 0x8c, 0x26, 0x04, 0xc0, 0x68, // .n..y.....y.&..h - 0x82, 0x10, 0x8c, 0x26, 0x0c, 0xc2, 0x68, 0x02, 0x31, 0x8c, 0x18, 0x3c, 0x00, 0x08, 0x82, 0x41, // ...&..h.1..<...A - 0x54, 0x23, 0xf8, 0x71, 0x1e, 0xe5, 0x21, 0x1e, 0xe1, 0x91, 0x1e, 0xe9, 0x01, 0x23, 0x30, 0x62, // T#.q..!......#0b - 0x1f, 0xe9, 0x31, 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x9a, 0x30, 0x08, 0xa3, 0x09, 0xc4, // ..1.....B0.0.... - 0x30, 0x9a, 0xc0, 0x17, 0xc2, 0x70, 0x44, 0xe0, 0x1e, 0xc1, 0x37, 0xcb, 0xf0, 0x40, 0x81, 0x2d, // 0....pD...7..@.- - 0x88, 0x7c, 0x6c, 0x49, 0xe4, 0x63, 0x8b, 0x22, 0x9f, 0x59, 0x02, 0x68, 0xa0, 0x62, 0x30, 0x1e, // .|lI.c.".Y.h.b0. - 0xce, 0x19, 0xa8, 0x18, 0x8c, 0x87, 0x73, 0x06, 0x2a, 0x06, 0xe3, 0xe1, 0x9c, 0xe1, 0x08, 0x24, // ......s.*......$ - 0x3f, 0x82, 0x6f, 0x96, 0x21, 0x92, 0x82, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0xa0, 0xc4, 0xa4, // ?.o.!......A0... - 0x44, 0xe2, 0xe3, 0x44, 0x46, 0x13, 0x02, 0xc0, 0x8c, 0x20, 0x3e, 0xa3, 0x09, 0x83, 0x60, 0x42, // D..DF.... >...`B - 0x40, 0x1f, 0x0b, 0xfc, 0x03, 0x3e, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0xa0, 0xac, 0x49, 0x8a, // @....>#....`..I. - 0x04, 0xfe, 0x31, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x0a, 0x9b, 0xa4, 0x48, 0xd0, 0x1f, 0xa3, // ..1bp. .....H... - 0x09, 0x48, 0x60, 0x4d, 0x10, 0x1f, 0x0b, 0x0e, 0xfa, 0x58, 0x40, 0x22, 0xf0, 0x19, 0x31, 0x38, // .H`M.....X@"..18 - 0x00, 0x10, 0x04, 0x03, 0x25, 0x4e, 0x5e, 0x24, 0x20, 0x91, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, // ....%N^$ .....A0 - 0x50, 0xe4, 0xe4, 0x45, 0x82, 0x11, 0x31, 0x12, 0x09, 0xe2, 0x63, 0x01, 0x22, 0x9f, 0x59, 0x02, // P..E..1...c.".Y. - 0x69, 0xa0, 0x62, 0x10, 0x22, 0x34, 0x80, 0x06, 0x2a, 0x06, 0x22, 0x42, 0x03, 0x68, 0xa0, 0x62, // i.b."4..*."B.h.b - 0x30, 0x22, 0x34, 0x80, 0x06, 0x2a, 0x06, 0x24, 0x22, 0x05, 0x68, 0xc4, 0xe0, 0x00, 0x40, 0x10, // 0"4..*.$".h...@. - 0x0c, 0x28, 0x3b, 0xc9, 0x91, 0x12, 0x81, 0x93, 0xd1, 0x84, 0x00, 0x18, 0x4d, 0x10, 0x82, 0xd1, // .(;.........M... - 0x84, 0x41, 0x18, 0x4d, 0x20, 0x06, 0x33, 0x83, 0xf1, 0x90, 0x8f, 0x05, 0x8a, 0x7c, 0x2c, 0x30, // .A.M .3......|,0 - 0xe4, 0x63, 0x68, 0x60, 0x1e, 0xf2, 0xb1, 0x80, 0x91, 0x8f, 0x05, 0x88, 0x7c, 0x4c, 0x0d, 0xd2, // .ch`........|L.. - 0x43, 0x3e, 0x16, 0x38, 0xf2, 0xb1, 0x40, 0x91, 0x8f, 0xb1, 0x01, 0x7b, 0xc8, 0xc7, 0x02, 0x48, // C>.8..@....{...H - 0x3e, 0x16, 0x30, 0xf2, 0x99, 0x25, 0x98, 0x06, 0x6a, 0x06, 0x52, 0x09, 0xc4, 0xa3, 0xd0, 0x07, // >.0..%..j.R..... - 0x86, 0x92, 0xf0, 0xa8, 0xb1, 0x23, 0x63, 0xa0, 0x66, 0x20, 0x95, 0x00, 0x3c, 0x0a, 0x7d, 0x60, // .....#c.f ..<.}` - 0x20, 0x49, 0x8f, 0x1a, 0x3c, 0x32, 0x06, 0x6a, 0x06, 0x52, 0x09, 0x7c, 0xa3, 0xd0, 0x07, 0x86, // I..<2.j.R.|.... - 0x91, 0xf8, 0xa8, 0xd1, 0x23, 0x63, 0xa0, 0x66, 0x20, 0x95, 0x80, 0x37, 0x0a, 0x7d, 0x60, 0x10, // ....#c.f ..7.}`. - 0xc9, 0x8f, 0x1a, 0x3e, 0x32, 0x46, 0x0c, 0x12, 0x00, 0x04, 0xc1, 0x00, 0x79, 0x15, 0x34, 0x39, // ...>2F......y.49 - 0x95, 0x53, 0xe9, 0x13, 0x62, 0xc4, 0x20, 0x01, 0x40, 0x10, 0x0c, 0x90, 0x57, 0x41, 0x93, 0x53, // .S..b. .@...WA.S - 0x39, 0x95, 0x3a, 0x19, 0x46, 0x0c, 0x12, 0x00, 0x04, 0xc1, 0x00, 0x79, 0x15, 0x34, 0x39, 0x95, // 9.:.F......y.49. - 0x53, 0xe1, 0x13, 0x61, 0xc4, 0x20, 0x01, 0x40, 0x10, 0x0c, 0x90, 0x57, 0x41, 0x93, 0x53, 0x39, // S..a. .@...WA.S9 - 0x95, 0x3f, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, // .?........... + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x49, 0x4c, 0x44, 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x30, 0x31, 0x33, // .ILDN,.....$.013 + 0x63, 0x31, 0x31, 0x62, 0x31, 0x32, 0x65, 0x63, 0x32, 0x31, 0x31, 0x66, 0x39, 0x63, 0x36, 0x34, // c11b12ec211f9c64 + 0x64, 0x39, 0x35, 0x30, 0x66, 0x33, 0x64, 0x63, 0x36, 0x39, 0x65, 0x64, 0x39, 0x2e, 0x70, 0x64, // d950f3dc69ed9.pd + 0x62, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // b....HASH....... + 0x00, 0x01, 0x3c, 0x11, 0xb1, 0x2e, 0xc2, 0x11, 0xf9, 0xc6, 0x4d, 0x95, 0x0f, 0x3d, 0xc6, 0x9e, // ..<.......M..=.. + 0xd9, 0x44, 0x58, 0x49, 0x4c, 0x28, 0x10, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x0a, 0x04, 0x00, // .DXIL(...`...... + 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, // .DXIL........... + 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x0b, 0x82, 0x20, // .BC..!......... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, // ...........#.A.. + 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, // I..29....%...... + 0x62, 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, // b..E.B..B..2.8.. + 0x4b, 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, // K.2b.H.. CF....2 + 0x42, 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, // B.H...#.PAQ..... + 0x8a, 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, // ..1F.Q.......... + 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, 0x0d, 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, // .....@.......... + 0x20, 0x6d, 0x30, 0x86, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, // m0..........I.. + 0x00, 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, // .......`B L..... + 0x00, 0x89, 0x20, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, // .. ......2".. d. + 0x04, 0x13, 0x23, 0xa4, 0x84, 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, // ..#....#......L. + 0x8c, 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0xa0, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, 0x66, 0x00, 0xe6, // ....L....@...f.. + 0x08, 0xc0, 0x60, 0x8e, 0x00, 0x29, 0xc6, 0x40, 0x10, 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, // ..`..).@.DA.Q.. + 0x88, 0x62, 0x20, 0xa4, 0x10, 0x03, 0x31, 0x0c, 0xa4, 0x94, 0x61, 0x20, 0x06, 0x62, 0x6e, 0x1a, // .b ...1...a .bn. + 0x2e, 0x7f, 0xc2, 0x1e, 0x42, 0xf2, 0x57, 0x42, 0x5a, 0x89, 0xc9, 0x2f, 0x6e, 0x1b, 0x15, 0xc3, // ....B.WBZ../n... + 0x30, 0x0c, 0x04, 0x15, 0xf7, 0x0c, 0x97, 0x3f, 0x61, 0x0f, 0x21, 0xf9, 0x21, 0xd0, 0x0c, 0x0b, // 0......?a.!.!... + 0x81, 0x82, 0xa7, 0x30, 0x0e, 0x01, 0x41, 0xc3, 0x30, 0x0c, 0x04, 0x41, 0x0c, 0x14, 0x1d, 0x35, // ...0..A.0..A...5 + 0x5c, 0xfe, 0x84, 0x3d, 0x84, 0xe4, 0x73, 0x1b, 0x55, 0xac, 0xc4, 0xe4, 0x17, 0xb7, 0x8d, 0x88, // ...=..s.U....... + 0x61, 0x18, 0x86, 0x42, 0x4c, 0x04, 0x44, 0x10, 0x55, 0x8a, 0x81, 0x18, 0x86, 0x81, 0xac, 0x39, // a..BL.D.U......9 + 0x82, 0xa0, 0x18, 0x10, 0x51, 0x10, 0x04, 0x46, 0xd9, 0x40, 0xc0, 0x30, 0x02, 0x31, 0xcc, 0xd4, // ....Q..F.@.0.1.. + 0x06, 0xe3, 0xc0, 0x0e, 0xe1, 0x30, 0x0f, 0xf3, 0xe0, 0x06, 0xb4, 0x50, 0x0e, 0xf8, 0x40, 0x0f, // .....0.....P..@. + 0xf5, 0x20, 0x0f, 0xe5, 0x20, 0x07, 0xa4, 0xc0, 0x07, 0xf6, 0x50, 0x0e, 0xe3, 0x40, 0x0f, 0xef, // . .. .....P..@.. + 0x20, 0x0f, 0x7c, 0x60, 0x0e, 0xec, 0xf0, 0x0e, 0xe1, 0x40, 0x0f, 0x6c, 0x00, 0x06, 0x74, 0xe0, // .|`.....@.l..t. + 0x07, 0x60, 0xe0, 0x07, 0x7a, 0xa0, 0x07, 0xed, 0x90, 0x0e, 0xf0, 0x30, 0x0f, 0xbf, 0x40, 0x0f, // .`..z......0..@. + 0xf9, 0x00, 0x0f, 0xe5, 0x80, 0x02, 0x62, 0x26, 0x31, 0x18, 0x07, 0x76, 0x08, 0x87, 0x79, 0x98, // ......b&1..v..y. + 0x07, 0x37, 0xa0, 0x85, 0x72, 0xc0, 0x07, 0x7a, 0xa8, 0x07, 0x79, 0x28, 0x07, 0x39, 0x20, 0x05, // .7..r..z..y(.9 . + 0x3e, 0xb0, 0x87, 0x72, 0x18, 0x07, 0x7a, 0x78, 0x07, 0x79, 0xe0, 0x03, 0x73, 0x60, 0x87, 0x77, // >..r..zx.y..s`.w + 0x08, 0x07, 0x7a, 0x60, 0x03, 0x30, 0xa0, 0x03, 0x3f, 0x00, 0x03, 0x3f, 0x40, 0x02, 0xd7, 0x91, // ..z`.0..?..?@... + 0x37, 0x8c, 0x30, 0x0c, 0x39, 0x00, 0xce, 0x34, 0xa1, 0xc3, 0x3b, 0xcc, 0x03, 0x3d, 0xb0, 0x43, // 7.0.9..4..;..=.C + 0x38, 0xe4, 0xc3, 0x3b, 0xd4, 0x03, 0x3d, 0xb8, 0x01, 0x19, 0x9c, 0x02, 0x3b, 0xbc, 0x83, 0x38, // 8..;..=.....;..8 + 0x84, 0x03, 0x3b, 0xcc, 0x03, 0x4a, 0xc2, 0x90, 0xe3, 0x38, 0x8e, 0xe3, 0x38, 0x12, 0x6f, 0x92, // ..;..J...8..8.o. + 0xa6, 0x88, 0x12, 0x26, 0x9f, 0x05, 0x98, 0x67, 0x21, 0x22, 0x76, 0x02, 0x26, 0x02, 0x05, 0x04, // ...&...g!"v.&... + 0x91, 0xc9, 0x40, 0x4c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, // ..@L.......r..t` + 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, // .6h.yh.r....P.m. + 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, // .zP.m..z0.r..s . + 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, // m..q..s .m..x..s + 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, // .m..q`.z0.r...0 + 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, // .r..s .m..v@.z`. + 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, // t.....v..s .m`.s + 0x20, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, // .z0.r...`.t..v@ + 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, // .m..x..q`.z0.r.. + 0x76, 0x40, 0x07, 0x43, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // v@.C............ + 0x86, 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x10, // .<............y. + 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x28, 0x40, 0x00, 0x04, // ...........(@.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe4, 0x61, 0x80, 0x00, 0x08, 0x00, 0x00, 0x00, // .......0.a...... + 0x00, 0x00, 0x00, 0x00, 0x60, 0xc8, 0x13, 0x01, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....`....0...... + 0x00, 0xc0, 0x90, 0x87, 0x02, 0x02, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x21, // ......`........! + 0x8f, 0x05, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x9e, 0x0c, 0x08, // ...@........C... + 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x40, 0x00, 0x00, 0x11, 0x00, 0x00, // ..........@..... + 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, // .2.....L...&G..C + 0x22, 0x4a, 0x60, 0x04, 0xa0, 0x28, 0x8a, 0xa1, 0x08, 0x4a, 0xa2, 0x40, 0x07, 0xca, 0xa0, 0x3c, // "J`..(...J.@...< + 0xa8, 0x28, 0x89, 0x32, 0x28, 0x84, 0x11, 0x80, 0x22, 0x28, 0x10, 0xfa, 0x66, 0x00, 0x68, 0x9c, // .(.2(..."(..f.h. + 0x01, 0xa0, 0x72, 0x06, 0x80, 0xcc, 0xb1, 0x18, 0x85, 0x00, 0x00, 0x00, 0x78, 0x9e, 0xe7, 0x79, // ..r.........x..y + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x1a, 0x03, 0x4c, // .....y...x.....L + 0x90, 0x46, 0x02, 0x13, 0xc4, 0x8f, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, 0x24, 0xc6, 0x25, 0x06, // .F.....o..M.$.%. + 0xc6, 0x45, 0xc6, 0x06, 0xa6, 0xc6, 0x65, 0xe6, 0x06, 0x04, 0x65, 0x8c, 0x86, 0x0c, 0xc7, 0x8c, // .E....e...e..... + 0xc6, 0x2c, 0x27, 0x27, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x88, 0x64, 0x82, 0x40, 0x28, 0x1b, 0x84, // .,''eC.L..d.@(.. + 0x81, 0x98, 0x20, 0x10, 0xcb, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, 0x4d, 0x10, 0x08, 0x66, 0xc3, // .. ...a0(..M..f. + 0x80, 0x24, 0xc4, 0x04, 0xe1, 0xc3, 0x08, 0x4c, 0x10, 0x88, 0x66, 0x82, 0x40, 0x38, 0x1b, 0x04, // .$.....L..f.@8.. + 0xc2, 0xd9, 0x90, 0x10, 0x0b, 0x43, 0x10, 0x43, 0x43, 0x3c, 0x1b, 0x92, 0x61, 0x61, 0x88, 0x61, // .....C.CC<..aa.a + 0x68, 0x88, 0x67, 0x43, 0xd2, 0x2c, 0x0c, 0xd1, 0x0c, 0x0d, 0xf1, 0x6c, 0x18, 0xa0, 0x48, 0x9a, // h.gC.,.....l..H. + 0x20, 0x8c, 0x41, 0x36, 0x41, 0x20, 0x9e, 0x0d, 0x08, 0x41, 0x31, 0x04, 0x31, 0x54, 0xc0, 0x86, // .A6A ...A1.1T.. + 0xc0, 0x9a, 0x20, 0x94, 0x81, 0xb6, 0x01, 0x21, 0x30, 0x86, 0x20, 0x06, 0x02, 0xd8, 0x80, 0x0c, // .. ....!0. ..... + 0x18, 0x43, 0x0c, 0x03, 0x01, 0x6c, 0x40, 0x1a, 0x8c, 0x21, 0x9a, 0x81, 0x00, 0x36, 0x0c, 0x99, // .C...l@..!...6.. + 0xb6, 0x6d, 0x20, 0x26, 0xe0, 0xe2, 0x26, 0x08, 0x66, 0xb0, 0x6d, 0x08, 0xbc, 0x09, 0x82, 0x00, // .m &..&.f.m..... + 0x90, 0x68, 0x0b, 0x4b, 0x73, 0xe3, 0x32, 0x65, 0xf5, 0x05, 0xf5, 0x36, 0x97, 0x46, 0x97, 0xf6, // .h.Ks.2e...6.F.. + 0xe6, 0x36, 0x41, 0x28, 0xa4, 0x09, 0x42, 0x31, 0x6d, 0x08, 0x88, 0x09, 0x42, 0x41, 0x4d, 0x10, // .6A(..B1m...BAM. + 0x8a, 0x6a, 0xc3, 0x42, 0x88, 0xc1, 0x18, 0x90, 0x41, 0x19, 0x98, 0xc1, 0x60, 0x06, 0xc4, 0x19, // .j.B....A...`... + 0x00, 0x44, 0xa8, 0x8a, 0xb0, 0x86, 0x9e, 0x9e, 0xa4, 0x88, 0x26, 0x08, 0x85, 0x35, 0x41, 0x20, // .D........&..5A + 0xa0, 0x0d, 0xc2, 0x1a, 0xac, 0xc1, 0x86, 0x65, 0x48, 0x83, 0x31, 0x38, 0x83, 0x32, 0x50, 0x83, // .......eH.18.2P. + 0x41, 0x0d, 0x86, 0x33, 0x60, 0x83, 0x0d, 0xc1, 0xb0, 0x61, 0x69, 0xd2, 0x60, 0x0c, 0xce, 0xc0, // A..3`....ai.`... + 0x0d, 0xd4, 0x60, 0x50, 0x83, 0x41, 0x0d, 0xd8, 0x60, 0x43, 0xd0, 0x6c, 0x58, 0xd6, 0x20, 0x0d, // ..`P.A..`C.lX. . + 0xc6, 0xe0, 0x0c, 0xe0, 0x40, 0x0d, 0x06, 0x35, 0x68, 0xce, 0x80, 0x0d, 0x36, 0x10, 0x68, 0xd0, // ....@..5h...6.h. + 0x06, 0x6f, 0x10, 0x07, 0x4c, 0xa6, 0xac, 0xbe, 0xa8, 0xc2, 0xe4, 0xce, 0xca, 0xe8, 0x26, 0x08, // .o..L.........&. + 0xc5, 0x35, 0x41, 0x20, 0xa2, 0x0d, 0xc2, 0x1a, 0xd4, 0xc1, 0x86, 0x85, 0x98, 0x83, 0x31, 0xa0, // .5A ..........1. + 0x83, 0x32, 0x38, 0x83, 0xc1, 0x0c, 0x88, 0x33, 0xb0, 0x83, 0x0d, 0xc1, 0x1d, 0x6c, 0x18, 0xe4, // .28....3.....l.. + 0x00, 0x0f, 0x80, 0x0d, 0x05, 0x18, 0x84, 0x41, 0x1e, 0x74, 0x40, 0x15, 0x36, 0x36, 0xbb, 0x36, // .......A.t@.66.6 + 0x97, 0x34, 0xb2, 0x32, 0x37, 0xba, 0x29, 0x41, 0x50, 0x85, 0x0c, 0xcf, 0xc5, 0xae, 0x4c, 0x6e, // .4.27.)AP.....Ln + 0x2e, 0xed, 0xcd, 0x6d, 0x4a, 0x40, 0x34, 0x21, 0xc3, 0x73, 0xb1, 0x0b, 0x63, 0xb3, 0x2b, 0x93, // ...mJ@4!.s..c.+. + 0x9b, 0x12, 0x18, 0x75, 0xc8, 0xf0, 0x5c, 0xe6, 0xd0, 0xc2, 0xc8, 0xca, 0xe4, 0x9a, 0xde, 0xc8, // ...u............ + 0xca, 0xd8, 0xa6, 0x04, 0x49, 0x19, 0x32, 0x3c, 0x17, 0xb9, 0xb2, 0xb9, 0xb7, 0x3a, 0xb9, 0xb1, // ....I.2<.....:.. + 0xb2, 0xb9, 0x29, 0x01, 0x57, 0x87, 0x0c, 0xcf, 0xc5, 0x2e, 0xad, 0xec, 0x2e, 0x89, 0x6c, 0x8a, // ..).W.........l. + 0x2e, 0x8c, 0xae, 0x6c, 0x4a, 0xe0, 0xd5, 0x21, 0xc3, 0x73, 0x29, 0x73, 0xa3, 0x93, 0xcb, 0x83, // ...lJ..!.s)s.... + 0x7a, 0x4b, 0x73, 0xa3, 0x9b, 0x9b, 0x12, 0xe4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x79, 0x18, 0x00, // zKs..........y.. + 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, // .Q...3......f..= + 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, // .C8...B..yx.s.q. + 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, // .........3.B.... + 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, // ...f0.=.C8.....= + 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, // .C=..=.x.tp.{..y + 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, // H.pp.zp.vx.p ... + 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, // ......0.n0.....P + 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, // .3....!..!..a.f0 + 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, // .;..;.C9..<..<.. + 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, // ;...v`.{h.7h.rh. + 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, // 7..p..p`.v(.v..v + 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, // x.w.._..q..r..y. + 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, // .,..........0.b. + 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, // ............a..! + 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, // .....a...C9.C9.C + 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, // 9.C9..8.C8..;../ + 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, // ..<..;..;....!.| + 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87, 0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, // p.z(.v....C..... + 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10, 0x0e, 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, // ..............P + 0x0f, 0xf4, 0x30, 0x83, 0x81, 0xc8, 0x01, 0x1f, 0xdc, 0x40, 0x1c, 0xe4, 0xa1, 0x1c, 0xc2, 0x61, // ..0......@.....a + 0x1d, 0xdc, 0x40, 0x1c, 0xe4, 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x23, 0x00, 0x00, // ..@......q ..#.. + 0x00, 0x36, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0x7e, 0x80, 0x34, 0x40, 0x84, 0xf9, 0xc5, 0x6d, 0x9b, // .6....<~.4@...m. + 0xc1, 0x36, 0x5c, 0xbe, 0xf3, 0xf8, 0x42, 0x40, 0x15, 0x05, 0x11, 0x95, 0x0e, 0x30, 0x94, 0x84, // .6....B@.....0.. + 0x01, 0x08, 0x98, 0x5f, 0xdc, 0xb6, 0x21, 0x48, 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x44, 0x04, 0x30, // ..._..!H..;./D.0 + 0x11, 0x21, 0xd0, 0x0c, 0x0b, 0x61, 0x01, 0xd3, 0x70, 0xf9, 0xce, 0xe3, 0x2f, 0x0e, 0x30, 0x88, // .!...a..p.../.0. + 0xcd, 0x43, 0x4d, 0x7e, 0x71, 0xdb, 0x56, 0x00, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x04, 0x30, 0xcf, // .CM~q.V....<..0. + 0x42, 0xf8, 0xc5, 0x6d, 0x9b, 0x40, 0x35, 0x5c, 0xbe, 0xf3, 0xf8, 0xd2, 0xe4, 0x44, 0x04, 0x4a, // B..m.@5......D.J + 0x4d, 0x0f, 0x35, 0xf9, 0xc5, 0x6d, 0xdb, 0x81, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x13, 0x11, 0x4d, // M.5..m..4......M + 0x08, 0x10, 0x61, 0x7e, 0x71, 0xdb, 0x46, 0xf0, 0x0c, 0x97, 0xef, 0x3c, 0x3e, 0xd5, 0x00, 0x11, // ..a~q.F....<>... + 0xe6, 0x17, 0xb7, 0x6d, 0x00, 0x04, 0x03, 0x20, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x61, 0x20, 0x00, // ...m... .....a . + 0x00, 0x36, 0x02, 0x00, 0x00, 0x13, 0x04, 0x56, 0x2c, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, // .6.....V,....... + 0x00, 0x84, 0x8d, 0x00, 0x50, 0x51, 0x02, 0x44, 0x14, 0x46, 0x39, 0x14, 0x4c, 0x41, 0x94, 0x45, // ....PQ.D.F9.LA.E + 0xe1, 0x95, 0xdd, 0x0c, 0x40, 0x19, 0x15, 0x52, 0x21, 0x94, 0x5c, 0x71, 0x95, 0x02, 0x0d, 0x63, // ....@..R!..q...c + 0x04, 0x20, 0x08, 0x82, 0x28, 0x18, 0x8c, 0x11, 0x80, 0x20, 0x08, 0x92, 0x60, 0x30, 0x46, 0x00, // . ..(.... ..`0F. + 0x82, 0x20, 0x88, 0xff, 0xc2, 0x18, 0x01, 0x08, 0x82, 0x20, 0x08, 0x06, 0x63, 0x04, 0x20, 0x08, // . ....... ..c. . + 0x82, 0xf0, 0x37, 0x02, 0x30, 0x03, 0x30, 0x46, 0x00, 0x82, 0x20, 0x88, 0x7f, 0x00, 0x00, 0x00, // ..7.0.0F.. ..... + 0x00, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0xb1, 0xc1, 0xf2, 0x9d, 0xc1, 0x19, 0x60, 0x23, // .#....`.......`# + 0x06, 0x09, 0x00, 0x82, 0x60, 0x90, 0xb5, 0x01, 0x03, 0x06, 0x6c, 0xc0, 0x06, 0xd9, 0x88, 0x41, // ....`.....l....A + 0x02, 0x80, 0x20, 0x18, 0x64, 0x6e, 0xd0, 0x84, 0x01, 0x1b, 0xb0, 0x81, 0x36, 0x62, 0x90, 0x00, // .. .dn......6b.. + 0x20, 0x08, 0x06, 0xd9, 0x1b, 0x38, 0x64, 0xa0, 0x06, 0x6a, 0xb0, 0x8d, 0x18, 0x24, 0x00, 0x08, // ....8d..j...$.. + 0x82, 0x41, 0x06, 0x07, 0x4f, 0x19, 0xbc, 0xc1, 0x1b, 0x70, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, // .A..O....p#....` + 0x90, 0xc5, 0x01, 0x64, 0x06, 0x6f, 0xf0, 0x06, 0xdd, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x64, // ...d.o....A.. .d + 0x72, 0x10, 0x91, 0x01, 0x1c, 0xc0, 0x81, 0x37, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x86, 0x1e, // r......7b.. .... + 0x4c, 0x6b, 0x10, 0x07, 0x66, 0x60, 0x8d, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0xb1, 0x07, 0x14, // Lk..f`..$....... + 0x1b, 0xc8, 0xc1, 0x77, 0x8d, 0x18, 0x24, 0x00, 0x08, 0x82, 0x81, 0xc1, 0x07, 0x15, 0x1c, 0xcc, // ...w..$......... + 0x01, 0x1a, 0x60, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0xf4, 0x81, 0x15, 0x07, 0x74, 0x10, // ..`#....``....t. + 0x06, 0xd9, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x18, 0x7e, 0x70, 0xd9, 0x41, 0x1d, 0xa8, 0x81, // ...A.. ..~p.A... + 0x36, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0xc6, 0x1f, 0x60, 0x77, 0x60, 0x07, 0x63, 0xb0, 0x8d, // 6b.. ....`w`.c.. + 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0xb5, 0x07, 0xdd, 0x71, 0x07, 0xa3, 0x09, 0x01, 0x30, 0x9a, // .........q....0. + 0x20, 0x04, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0x40, 0xf9, 0x01, 0x18, 0x28, 0x7b, 0x30, 0x9a, // .#....`@...({0. + 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x54, 0x28, 0x8c, 0x41, // ....B0bp. ..T(.A + 0xa3, 0x07, 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0x86, 0x2c, 0xf2, 0x19, 0x31, 0x40, 0x00, // .....0. ..,..1@. + 0x10, 0x04, 0x03, 0x8b, 0x14, 0xc2, 0xc0, 0x58, 0x02, 0x23, 0x02, 0xe8, 0x98, 0xe2, 0xc8, 0x67, // .......X.#.....g + 0xc4, 0x00, 0x01, 0x40, 0x10, 0x0c, 0xac, 0x53, 0x20, 0x03, 0xc4, 0x09, 0x2c, 0x30, 0xa0, 0x33, // ...@...S ...,0.3 + 0x62, 0x60, 0x00, 0x20, 0x08, 0x06, 0x0c, 0x2b, 0x90, 0x02, 0x31, 0x62, 0x60, 0x00, 0x20, 0x08, // b`. ...+..1b`. . + 0x06, 0x4c, 0x2b, 0x94, 0x82, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x14, 0x2b, 0xb8, 0x01, // .L+..0bp. ...+.. + 0x16, 0x07, 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0x56, 0x08, 0xf1, 0xb1, 0x42, 0x88, 0xcf, // .....0. .V...B.. + 0x68, 0x42, 0x21, 0x8c, 0x26, 0x18, 0x83, 0x11, 0x82, 0x7c, 0x8c, 0x10, 0xe4, 0x63, 0x6b, 0x20, // hB!.&....|...ck + 0xc4, 0xc7, 0xd8, 0x40, 0x88, 0xcf, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0xba, 0x70, 0x07, // ...@...... .(.p. + 0x02, 0x1b, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0xb2, 0x0b, 0x77, 0x10, 0xac, 0xc1, 0x88, // ...........w.... + 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0xbc, 0x90, 0x07, 0x83, 0x1b, 0x8c, 0x18, 0x1c, 0x00, 0x08, // ... .(.......... + 0x82, 0x81, 0xd2, 0x0b, 0x79, 0x10, 0xb4, 0x81, 0x05, 0x83, 0x7c, 0xac, 0x0c, 0xe6, 0x40, 0x3e, // ....y.....|...@> + 0x16, 0xd4, 0x01, 0x7c, 0x46, 0x0c, 0x0c, 0x00, 0x04, 0xc1, 0x80, 0x01, 0x07, 0x5c, 0x08, 0x4c, // ...|F..........L + 0x0e, 0x82, 0xf8, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0x05, 0x0e, 0xa2, 0xc0, 0x06, 0xbb, // ................ + 0x30, 0x9a, 0x10, 0x04, 0x16, 0x0c, 0xf2, 0x19, 0x31, 0x38, 0x00, 0x10, 0x04, 0x03, 0xc5, 0x1c, // 0.......18...... + 0x44, 0xc1, 0x0e, 0x82, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0x50, 0xce, 0x61, 0x14, 0xee, 0x80, // D.......A0P.a... + 0x0d, 0x2c, 0x10, 0xe4, 0x33, 0x9a, 0x60, 0x0c, 0xc3, 0x11, 0x01, 0x1f, 0x04, 0xdf, 0x2c, 0x43, // .,..3.`.......,C + 0x20, 0x04, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0x40, 0xa1, 0x83, 0x2a, 0xd0, 0x81, 0x2f, 0x8c, // .#....`@..*../. + 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0xb5, 0x0e, 0xad, // &..h............ + 0x70, 0x07, 0xa9, 0x30, 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, 0x30, 0x62, 0x70, 0x00, 0x20, 0x08, // p..0.....B0bp. . + 0x06, 0x94, 0x3b, 0xc0, 0x82, 0x1e, 0xa4, 0xc2, 0x68, 0x42, 0x00, 0x8c, 0x26, 0x08, 0x81, 0x21, // ..;.....hB..&..! + 0x78, 0x20, 0x9f, 0x11, 0x03, 0x04, 0x00, 0x41, 0x30, 0xb0, 0xe2, 0xc1, 0x15, 0x0c, 0x3c, 0x08, // x .....A0.....<. + 0x8c, 0x08, 0xa0, 0x63, 0xca, 0x1e, 0xc8, 0x67, 0xc4, 0x00, 0x01, 0x40, 0x10, 0x0c, 0x2c, 0x7a, // ...c...g...@..,z + 0x88, 0x05, 0x64, 0x0f, 0x02, 0x0b, 0x0c, 0xe8, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x01, 0x75, // ..d............u + 0x0f, 0xb9, 0x30, 0x0a, 0xef, 0x30, 0x9a, 0x10, 0x08, 0xa3, 0x09, 0x02, 0x30, 0x9a, 0x30, 0x04, // ..0..0......0.0. + 0x26, 0x0c, 0xf1, 0x31, 0x81, 0x88, 0xcf, 0x88, 0x81, 0x01, 0x80, 0x20, 0x18, 0x30, 0xff, 0x70, // &..1....... .0.p + 0x0f, 0xca, 0x88, 0x81, 0x01, 0x80, 0x20, 0x18, 0x30, 0x20, 0x81, 0x0f, 0x88, 0x09, 0x44, 0x7c, // ...... .0 ....D| + 0x4c, 0x20, 0xe2, 0x33, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x0a, 0x49, 0x84, 0x83, 0x10, 0x8c, // L .3bp. ...I.... + 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0x52, 0x12, 0xe1, 0x10, 0xdc, 0xc2, 0x88, 0xc1, 0x01, 0x80, // ......R......... + 0x20, 0x18, 0x28, 0x26, 0x31, 0x0e, 0x04, 0x2e, 0x8c, 0x18, 0x1c, 0x00, 0x08, 0x82, 0x81, 0x72, // .(&1..........r + 0x12, 0xe4, 0x40, 0xe4, 0x82, 0x09, 0x82, 0x7c, 0x4c, 0x10, 0xe4, 0x63, 0x81, 0x00, 0x9f, 0x11, // ..@....|L..c.... + 0x03, 0x03, 0x00, 0x41, 0x30, 0x60, 0x54, 0xc2, 0x1d, 0x82, 0xd1, 0x84, 0x34, 0x00, 0x2c, 0x00, // ...A0`T.....4.,. + 0x07, 0xf9, 0x58, 0x30, 0xc5, 0xc7, 0x02, 0x05, 0x3e, 0x16, 0x14, 0xf0, 0xb1, 0xa0, 0xa0, 0xcf, // ..X0....>....... + 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x28, 0x32, 0xf1, 0x0e, 0x01, 0x39, 0x8c, 0x18, 0x1c, 0x00, // .... .(2...9.... + 0x08, 0x82, 0x81, 0x32, 0x13, 0xef, 0x10, 0x8c, 0xc3, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x50, // ...2......... .P + 0x31, 0x31, 0x0f, 0xbd, 0x80, 0x0f, 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0xa3, 0x09, 0x83, // 11.......0. .... + 0x30, 0x9a, 0x40, 0x0c, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0x40, 0xd9, 0x04, 0x3e, 0x88, 0x03, // 0.@.#....`@..>.. + 0x4c, 0x8c, 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x8c, 0x26, 0x0c, 0xc2, 0x68, 0x02, 0x31, 0x58, // L.&..h...&..h.1X + 0x42, 0xc4, 0xc7, 0x12, 0x22, 0x3e, 0x96, 0x10, 0xf1, 0xb1, 0x84, 0x88, 0x8f, 0x11, 0x8f, 0x7c, // B...">.........| + 0x8c, 0x80, 0xe4, 0x63, 0x44, 0x24, 0x1f, 0x23, 0x24, 0xf9, 0x18, 0xc1, 0xc0, 0xc7, 0x08, 0x06, // ...cD$.#$....... + 0x3e, 0x46, 0x30, 0xf0, 0x31, 0x82, 0x81, 0x8f, 0x89, 0x02, 0x2b, 0xc8, 0xc7, 0x8a, 0x40, 0x3e, // >F0.1.....+...@> + 0x56, 0x08, 0xf2, 0xb1, 0x62, 0x90, 0x8f, 0x15, 0x84, 0x7c, 0x66, 0x09, 0xa6, 0xe1, 0x08, 0x53, // V...b....|f....S + 0xf8, 0x87, 0xe0, 0x9b, 0x65, 0x18, 0x8c, 0x60, 0xc4, 0xe0, 0x00, 0x40, 0x10, 0x0c, 0xa8, 0xb6, // ....e..`...@.... + 0x78, 0x89, 0x7c, 0x18, 0x8b, 0xd1, 0x84, 0x00, 0x18, 0x4d, 0x10, 0x82, 0x11, 0x83, 0x03, 0x00, // x.|......M...... + 0x41, 0x30, 0xa0, 0xe0, 0x42, 0x26, 0xf8, 0xc1, 0x25, 0x46, 0x13, 0x02, 0x60, 0x34, 0x41, 0x08, // A0..B&..%F..`4A. + 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, 0x80, 0x9a, 0x8b, 0x9a, 0xf8, 0x07, 0x97, 0x18, 0x4d, 0x08, // F.............M. + 0x80, 0xd1, 0x04, 0x21, 0x30, 0xa4, 0x1f, 0xe4, 0x33, 0x62, 0x80, 0x00, 0x20, 0x08, 0x06, 0x96, // ...!0...3b.. ... + 0x5d, 0xcc, 0x84, 0xd1, 0x0f, 0x81, 0x11, 0x01, 0x74, 0x4c, 0x01, 0x09, 0xf9, 0x8c, 0x18, 0x20, // ].......tL..... + 0x00, 0x08, 0x82, 0x81, 0x95, 0x17, 0x36, 0x81, 0x80, 0x44, 0x60, 0x81, 0x01, 0x9d, 0x11, 0x83, // ......6..D`..... + 0x03, 0x00, 0x41, 0x30, 0xa0, 0xf8, 0xc2, 0x27, 0x50, 0x82, 0x2e, 0x46, 0x13, 0x02, 0x60, 0x34, // ..A0...'P..F..`4 + 0x41, 0x08, 0xec, 0x10, 0xe8, 0x63, 0x85, 0x40, 0x9f, 0x11, 0x83, 0x07, 0x00, 0x41, 0x30, 0x88, // A....c.@.....A0. + 0x44, 0x83, 0x2c, 0x62, 0xc2, 0x25, 0x84, 0xa0, 0x26, 0x6a, 0xa2, 0x2f, 0xfa, 0x42, 0x2c, 0x6a, // D.,b.%..&j./.B,j + 0x62, 0x34, 0x21, 0x00, 0x46, 0x13, 0x84, 0x60, 0x34, 0x61, 0x10, 0x46, 0x13, 0x88, 0x61, 0x34, // b4!.F..`4a.F..a4 + 0xc1, 0x1c, 0x84, 0xe1, 0x88, 0x40, 0x27, 0x82, 0x6f, 0x96, 0x81, 0x28, 0x02, 0x33, 0x06, 0xf9, // .....@'.o..(.3.. + 0x98, 0x41, 0xc8, 0xc7, 0x8c, 0x42, 0x3e, 0xb3, 0x04, 0xc5, 0x40, 0xc5, 0x60, 0x10, 0xd2, 0x30, // .A...B>...@.`..0 + 0x50, 0x31, 0x18, 0x84, 0x34, 0x0c, 0x54, 0x0c, 0x06, 0x21, 0x0d, 0xc3, 0x11, 0x48, 0x59, 0x04, // P1..4.T..!...HY. + 0xdf, 0x74, 0x03, 0x31, 0x04, 0xd3, 0x0d, 0xc5, 0x20, 0x4c, 0x37, 0x18, 0xcc, 0x30, 0x62, 0x70, // .t.1.... L7..0bp + 0x00, 0x20, 0x08, 0x06, 0x94, 0x6c, 0xd0, 0x85, 0x4f, 0xb0, 0xc6, 0x68, 0x42, 0x00, 0x8c, 0x26, // . ...l..O..hB..& + 0x08, 0xc1, 0x68, 0xc2, 0x20, 0x8c, 0x26, 0x10, 0x83, 0xc9, 0x03, 0x3f, 0xc8, 0xc7, 0x9a, 0x40, // ..h. .&....?...@ + 0x3e, 0x16, 0x18, 0xf2, 0xb1, 0x65, 0x90, 0x8f, 0x05, 0x87, 0x7c, 0x8c, 0x29, 0xe4, 0x63, 0x01, // >....e....|.).c. + 0x22, 0x1f, 0x6b, 0x0e, 0xf9, 0x58, 0x90, 0xc8, 0x67, 0x96, 0x60, 0x1a, 0x8e, 0xd0, 0x87, 0xbb, // ".k..X..g.`..... + 0x08, 0xbe, 0x59, 0x86, 0xe9, 0x08, 0x86, 0x23, 0xf6, 0x41, 0x2f, 0x82, 0x6f, 0x96, 0x01, 0x69, // ..Y....#.A/.o..i + 0x82, 0x11, 0x83, 0x07, 0x00, 0x41, 0x30, 0x88, 0xc6, 0xa3, 0x34, 0xe4, 0xe2, 0x2d, 0xd6, 0x42, // .....A0...4..-.B + 0x2d, 0xec, 0xc2, 0x2e, 0x7c, 0xc3, 0x37, 0x46, 0xc3, 0x2e, 0x46, 0x13, 0x02, 0x60, 0x34, 0x41, // -...|.7F..F..`4A + 0x08, 0x46, 0x13, 0x06, 0x61, 0x34, 0x81, 0x18, 0x46, 0x13, 0x4e, 0x42, 0x18, 0x8e, 0x08, 0xf6, // .F..a4..F.NB.... + 0x22, 0xf8, 0x66, 0x19, 0x12, 0x25, 0x30, 0x63, 0x90, 0x8f, 0x19, 0x84, 0x7c, 0xcc, 0x28, 0xe4, // ".f..%0c....|.(. + 0x33, 0x4b, 0xa0, 0x0c, 0x54, 0x0c, 0x46, 0x22, 0x21, 0x03, 0x15, 0x83, 0x91, 0x48, 0xc8, 0x40, // 3K..T.F"!....H.@ + 0xc5, 0x60, 0x24, 0x12, 0x32, 0x1c, 0x81, 0x98, 0x46, 0xf0, 0xcd, 0x32, 0x2c, 0x4c, 0x30, 0x62, // .`$.2...F..2,L0b + 0x70, 0x00, 0x20, 0x08, 0x06, 0x14, 0x7c, 0xc8, 0x06, 0x5f, 0xd0, 0xc6, 0x68, 0x42, 0x00, 0x98, // p. ...|.._..hB.. + 0x11, 0xc4, 0x67, 0x34, 0x61, 0x10, 0x4c, 0x08, 0xe8, 0x63, 0xc1, 0x6a, 0xc0, 0x67, 0xc4, 0xe0, // ..g4a.L..c.j.g.. + 0x00, 0x40, 0x10, 0x0c, 0x94, 0xfc, 0xb0, 0x8d, 0x60, 0x35, 0x46, 0x0c, 0x0e, 0x00, 0x04, 0xc1, // .@......`5F..... + 0x40, 0xd1, 0x0f, 0xdb, 0x08, 0x54, 0x63, 0x34, 0x01, 0x09, 0xac, 0x09, 0xe2, 0x63, 0xc1, 0x41, // @....Tc4.....c.A + 0x1f, 0x0b, 0x62, 0x03, 0x3e, 0x23, 0x06, 0x07, 0x00, 0x82, 0x60, 0xa0, 0xfc, 0x07, 0x6f, 0x04, // ..b.>#....`...o. + 0xb1, 0x31, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, 0x0a, 0x88, 0xf0, 0x46, 0x00, 0x1b, 0x16, 0x1b, // .1bp. .....F.... + 0x41, 0x7c, 0x2c, 0x40, 0xe4, 0x33, 0x4b, 0xc0, 0x0c, 0x54, 0x0c, 0xc2, 0x82, 0x06, 0xca, 0x40, // A|,@.3K..T.....@ + 0xc5, 0x40, 0x2c, 0x68, 0xa0, 0x0c, 0x54, 0x0c, 0xc6, 0x82, 0x06, 0xca, 0x40, 0xc5, 0x80, 0x2c, // .@,h..T.....@.., + 0x7a, 0xa0, 0x18, 0x91, 0x16, 0xf2, 0x31, 0x42, 0x2d, 0xe4, 0x63, 0xc4, 0x5a, 0xc8, 0xc7, 0x08, // z.....1B-.c.Z... + 0xb6, 0x90, 0xcf, 0x88, 0xc1, 0x01, 0x80, 0x20, 0x18, 0x50, 0x28, 0xa2, 0x1e, 0xb4, 0x21, 0x22, // ....... .P(...!" + 0xa3, 0x09, 0x01, 0x30, 0x9a, 0x20, 0x04, 0xa3, 0x09, 0x83, 0x30, 0x9a, 0x40, 0x0c, 0x96, 0x10, // ...0. ....0.@... + 0xf2, 0xb1, 0x84, 0x90, 0x8f, 0x25, 0x84, 0x7c, 0x2c, 0x21, 0xe4, 0x33, 0x4b, 0x30, 0x0d, 0x47, // .....%.|,!.3K0.G + 0xac, 0xc5, 0x7a, 0x04, 0xdf, 0x2c, 0x83, 0x33, 0x05, 0x23, 0x06, 0x0f, 0x00, 0x82, 0x60, 0x10, // ..z..,.3.#....`. + 0xcd, 0x48, 0x7d, 0x88, 0xc7, 0x6f, 0xec, 0x86, 0x6e, 0x98, 0x87, 0x79, 0xb8, 0x88, 0x8b, 0xcc, // .H}..o..n..y.... + 0x87, 0x79, 0x8c, 0x26, 0x04, 0xc0, 0x68, 0x82, 0x10, 0x8c, 0x26, 0x0c, 0xc2, 0x68, 0x02, 0x31, // .y.&..h...&..h.1 + 0x8c, 0x18, 0x3c, 0x00, 0x08, 0x82, 0x41, 0x84, 0x23, 0xfa, 0x81, 0x1e, 0xe5, 0x21, 0x1e, 0xe1, // ..<...A.#....!.. + 0xb1, 0x1e, 0xeb, 0x31, 0x23, 0x33, 0x82, 0x1f, 0xeb, 0x31, 0x9a, 0x10, 0x00, 0xa3, 0x09, 0x42, // ...1#3...1.....B + 0x30, 0x9a, 0x30, 0x08, 0xa3, 0x09, 0xc4, 0x30, 0x9a, 0xc0, 0x17, 0xc2, 0x70, 0x44, 0x00, 0x1f, // 0.0....0....pD.. + 0xc1, 0x37, 0xcb, 0xf0, 0x40, 0x81, 0x2d, 0x88, 0x7c, 0x6c, 0x49, 0xe4, 0x63, 0x8b, 0x22, 0x9f, // .7..@.-.|lI.c.". + 0x59, 0x02, 0x68, 0xa0, 0x62, 0x30, 0x1e, 0xce, 0x19, 0xa8, 0x18, 0x8c, 0x87, 0x73, 0x06, 0x2a, // Y.h.b0.......s.* + 0x06, 0xe3, 0xe1, 0x9c, 0xe1, 0x08, 0x64, 0x3f, 0x82, 0x6f, 0x96, 0x21, 0x92, 0x82, 0x11, 0x83, // ......d?.o.!.... + 0x03, 0x00, 0x41, 0x30, 0xa0, 0xca, 0xe4, 0x44, 0xe2, 0x23, 0x45, 0x46, 0x13, 0x02, 0xc0, 0x8c, // ..A0...D.#EF.... + 0x20, 0x3e, 0xa3, 0x09, 0x83, 0x60, 0x42, 0x40, 0x1f, 0x0b, 0x40, 0x04, 0x3e, 0x23, 0x06, 0x07, // >...`B@..@.>#.. + 0x00, 0x82, 0x60, 0xa0, 0xb8, 0xc9, 0x8a, 0x04, 0x20, 0x32, 0x62, 0x70, 0x00, 0x20, 0x08, 0x06, // ..`..... 2bp. .. + 0xca, 0x9b, 0xac, 0x48, 0xf0, 0x1f, 0xa3, 0x09, 0x48, 0x60, 0x4d, 0x10, 0x1f, 0x0b, 0x0e, 0xfa, // ...H....H`M..... + 0x58, 0x60, 0x22, 0xf0, 0x19, 0x31, 0x38, 0x00, 0x10, 0x04, 0x03, 0x85, 0x4e, 0x62, 0x24, 0x30, // X`"..18.....Nb$0 + 0x91, 0x11, 0x83, 0x03, 0x00, 0x41, 0x30, 0x50, 0xea, 0x24, 0x46, 0x82, 0x12, 0x31, 0x13, 0x09, // .....A0P.$F..1.. + 0xe2, 0x63, 0x01, 0x22, 0x9f, 0x59, 0x02, 0x69, 0xa0, 0x62, 0x10, 0x22, 0x34, 0x80, 0x06, 0x2a, // .c.".Y.i.b."4..* + 0x06, 0x22, 0x42, 0x03, 0x68, 0xa0, 0x62, 0x30, 0x22, 0x34, 0x80, 0x06, 0x2a, 0x06, 0x24, 0x22, // ."B.h.b0"4..*.$" + 0x05, 0x68, 0xc4, 0xe0, 0x00, 0x40, 0x10, 0x0c, 0xa8, 0x3c, 0xd9, 0x91, 0x12, 0x99, 0x93, 0xd1, // .h...@...<...... + 0x84, 0x00, 0x18, 0x4d, 0x10, 0x82, 0xd1, 0x84, 0x41, 0x18, 0x4d, 0x20, 0x06, 0x33, 0x83, 0xf1, // ...M....A.M .3.. + 0x90, 0x8f, 0x05, 0x8a, 0x7c, 0x2c, 0x30, 0xe4, 0x63, 0x68, 0x60, 0x1e, 0xf2, 0xb1, 0x80, 0x91, // ....|,0.ch`..... + 0x8f, 0x05, 0x88, 0x7c, 0x4c, 0x0d, 0xd2, 0x43, 0x3e, 0x16, 0x38, 0xf2, 0xb1, 0x40, 0x91, 0x8f, // ...|L..C>.8..@.. + 0xb1, 0x01, 0x7b, 0xc8, 0xc7, 0x02, 0x48, 0x3e, 0x16, 0x30, 0xf2, 0x99, 0x25, 0x98, 0x06, 0x6a, // ..{...H>.0..%..j + 0x06, 0x52, 0x09, 0xc4, 0xa3, 0xd0, 0x07, 0x86, 0x92, 0xf8, 0xa8, 0xd1, 0x23, 0x63, 0xa0, 0x66, // .R..........#c.f + 0x20, 0x95, 0x00, 0x3c, 0x0a, 0x7d, 0x60, 0x20, 0xc9, 0x8f, 0x1a, 0x3e, 0x32, 0x06, 0x6a, 0x06, // ..<.}` ...>2.j. + 0x52, 0x09, 0x7c, 0xa3, 0xd0, 0x07, 0x86, 0x91, 0x40, 0xa9, 0xf1, 0x23, 0x63, 0xa0, 0x66, 0x20, // R.|.....@..#c.f + 0x95, 0x80, 0x37, 0x0a, 0x7d, 0x60, 0x10, 0x49, 0x94, 0x1a, 0x50, 0x32, 0x46, 0x0c, 0x0e, 0x00, // ..7.}`.I..P2F... + 0x04, 0xc1, 0x80, 0x72, 0x15, 0x38, 0xd1, 0x11, 0x3b, 0x19, 0x4d, 0x08, 0x84, 0xe1, 0x88, 0xa0, // ...r.8..;.M..... + 0x4c, 0x84, 0x6f, 0x38, 0x62, 0x3d, 0xd0, 0xc4, 0xf9, 0x4a, 0x08, 0x74, 0x96, 0x81, 0xaa, 0x82, // L.o8b=...J.t.... + 0xd1, 0x84, 0x02, 0x18, 0x4d, 0x30, 0x02, 0xe3, 0x11, 0x21, 0x3e, 0xc6, 0x23, 0x42, 0x7c, 0x46, // ....M0...!>.#B|F + 0x13, 0x92, 0xc1, 0x86, 0x84, 0x3e, 0x36, 0x08, 0xf4, 0x19, 0x8e, 0x10, 0xde, 0x84, 0xf8, 0xa6, // .....>6......... + 0x1b, 0xe0, 0xc4, 0x4d, 0x82, 0xe1, 0x88, 0x21, 0x4e, 0x88, 0x6f, 0xba, 0x41, 0x4e, 0xe0, 0x24, // ...M...!N.o.AN.$ + 0x18, 0x8e, 0x30, 0xe2, 0x84, 0xf8, 0xa6, 0x1b, 0xe4, 0x84, 0x4e, 0x82, 0xe1, 0x88, 0x63, 0x4e, // ..0.......N...cN + 0x88, 0x6f, 0xba, 0x81, 0x4e, 0xec, 0x24, 0x18, 0x31, 0x78, 0x00, 0x10, 0x04, 0x83, 0x68, 0x5c, // .o..N.$.1x....h. + 0x4a, 0x85, 0x4e, 0xe2, 0x44, 0x49, 0xec, 0xc4, 0x4e, 0x7c, 0xc5, 0x57, 0x46, 0xc5, 0x4e, 0x46, // J.N.DI..N|.WF.NF + 0x13, 0x82, 0xc1, 0x8a, 0x44, 0x3e, 0x16, 0x20, 0xf2, 0xb1, 0xa0, 0x90, 0x8f, 0x05, 0x84, 0x7c, // ....D>. .......| + 0x2c, 0xf0, 0xe4, 0x63, 0x82, 0x27, 0x1f, 0x1b, 0x3c, 0xf9, 0x18, 0xe1, 0xc9, 0x67, 0x96, 0xa0, // ,..c.'..<....g.. + 0x1a, 0xa8, 0x18, 0x10, 0x8a, 0x14, 0xa6, 0x81, 0x8a, 0x01, 0xa1, 0x48, 0x61, 0x1a, 0xa8, 0x18, // ...........Ha... + 0x10, 0x8a, 0x14, 0xa6, 0x81, 0x8a, 0x01, 0xa1, 0x48, 0x61, 0x1a, 0x31, 0x48, 0x00, 0x10, 0x04, // ........Ha.1H... + 0x03, 0x84, 0x5e, 0x56, 0x85, 0x5d, 0xd8, 0x25, 0x5c, 0x88, 0x11, 0x83, 0x04, 0x00, 0x41, 0x30, // ..^V.].%......A0 + 0x40, 0xe8, 0x65, 0x55, 0xd8, 0x85, 0x5d, 0x72, 0x65, 0x18, 0x31, 0x48, 0x00, 0x10, 0x04, 0x03, // @.eU..]re.1H.... + 0x84, 0x5e, 0x56, 0x85, 0x5d, 0xd8, 0x05, 0x5c, 0x84, 0x11, 0x83, 0x04, 0x00, 0x41, 0x30, 0x40, // .^V.]........A0@ + 0xe8, 0x65, 0x55, 0xd8, 0x85, 0x5d, 0xc6, 0x25, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .eU..].%@....... + 0x00, 0x00, 0x00, 0xd0, 0x00, // ..... }; diff --git a/Polyfills/Canvas/Source/Shaders/essl/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/essl/fs_nanovg_fill.h index c0750d384..beb608a3f 100644 --- a/Polyfills/Canvas/Source/Shaders/essl/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/essl/fs_nanovg_fill.h @@ -1,23 +1,26 @@ -static const uint8_t fs_nanovg_fill_essl[4776] = +static const uint8_t fs_nanovg_fill_essl[5414] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x75, // FSH............u - 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, // _scissorMat..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, // ......u_scissorE - 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // xtScale......... - 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, // ..u_params...... - 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, // .....u_sdf...... - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x03, // .....u_paintMat. - 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, // ..........u_exte - 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // ntRadius........ - 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, // ...u_innerCol... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, // ........u_outerC - 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, // ol...........s_t - 0x65, 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, // ex...........s_t - 0x65, 0x78, 0x32, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x11, 0x00, // ex2............. + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .......u_scissor + 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, // Mat...........u_ + 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x02, // scissorExtScale. + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // ..........u_para + 0x6d, 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, // ms...........u_s + 0x64, 0x66, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, // df...........u_p + 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, // aintMat......... + 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // ..u_extentRadius + 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ...........u_inn + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol........... + 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, // u_outerCol...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // .....u_clipBound + 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // s...........s_te + 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, // x...........s_te + 0x78, 0x32, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x63, // x2...........s_c + 0x6c, 0x69, 0x70, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0x14, 0x00, // lip............. 0x00, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, // .precision mediu 0x6d, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, // mp float;.precis 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x69, 0x6e, 0x74, 0x3b, 0x0a, 0x0a, // ion highp int;.. - 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x34, 0x31, 0x3b, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x66, // vec4 _541;..unif + 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x33, 0x3b, 0x0a, 0x0a, 0x75, 0x6e, 0x69, 0x66, // vec4 _593;..unif 0x6f, 0x72, 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x75, // orm highp mat3 u 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, // _scissorMat;.uni 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // form highp vec4 @@ -37,265 +40,303 @@ static const uint8_t fs_nanovg_fill_essl[4776] = 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, // m highp sampler2 0x44, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // D s_tex;.uniform 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, // highp sampler2D - 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x3b, 0x0a, 0x0a, 0x69, 0x6e, 0x20, 0x68, 0x69, 0x67, // s_tex2;..in hig - 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, // hp vec2 v_positi - 0x6f, 0x6e, 0x3b, 0x0a, 0x69, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, // on;.in highp vec - 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x69, // 2 v_texcoord0;.i - 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x74, // n highp vec2 v_t - 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, // excoord1;.layout - 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, // (location = 0) o - 0x75, 0x74, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x62, 0x67, // ut highp vec4 bg - 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x76, // fx_FragColor;..v - 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, // oid main().{. - 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x34, 0x30, 0x32, // highp vec3 _402 - 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, // = vec3(v_positi - 0x6f, 0x6e, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // on, 1.0);. hi - 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x34, 0x31, 0x33, 0x20, 0x3d, 0x20, // ghp vec2 _413 = - 0x76, 0x65, 0x63, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x28, 0x61, 0x62, // vec2(0.5) - ((ab - 0x73, 0x28, 0x28, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x20, // s((u_scissorMat - 0x2a, 0x20, 0x5f, 0x34, 0x30, 0x32, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x75, 0x5f, // * _402).xy) - u_ - 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, // scissorExtScale. - 0x78, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, // xy) * u_scissorE - 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x7a, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // xtScale.zw);. - 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, 0x32, // highp float _42 - 0x30, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x31, 0x33, 0x2e, 0x78, // 0 = clamp(_413.x - 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x63, 0x6c, // , 0.0, 1.0) * cl - 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x31, 0x33, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // amp(_413.y, 0.0, - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, // 1.0);. highp - 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, 0x33, 0x36, 0x20, 0x3d, 0x20, 0x6d, 0x69, // float _436 = mi - 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x61, 0x62, // n(1.0, (1.0 - ab - 0x73, 0x28, 0x28, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x78, // s((v_texcoord0.x - 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x20, // * 2.0) - 1.0)) - 0x2a, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x20, 0x2a, 0x20, // * u_params.y) * - 0x6d, 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // min(1.0, v_texco - 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, // ord0.y);. hig - 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x33, 0x36, 0x3b, 0x0a, 0x20, 0x20, // hp vec4 _536;. - 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, // if (u_params.w - 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // == 0.0). {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, // highp vec - 0x32, 0x20, 0x5f, 0x34, 0x34, 0x38, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x75, 0x5f, // 2 _448 = abs((u_ - 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x30, 0x32, 0x29, // paintMat * _402) - 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, // .xy) - (u_extent - 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x20, 0x2d, 0x20, 0x76, 0x65, 0x63, 0x32, // Radius.xy - vec2 - 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, // (u_extentRadius. - 0x7a, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, // z));. _53 - 0x36, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, // 6 = mix(u_innerC - 0x6f, 0x6c, 0x2c, 0x20, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, // ol, u_outerCol, - 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x28, 0x6d, 0x69, // vec4(clamp((((mi - 0x6e, 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x34, 0x38, 0x2e, 0x78, 0x2c, 0x20, 0x5f, 0x34, // n(max(_448.x, _4 - 0x34, 0x38, 0x2e, 0x79, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x65, // 48.y), 0.0) + le - 0x6e, 0x67, 0x74, 0x68, 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x34, 0x38, 0x2c, 0x20, 0x76, // ngth(max(_448, v - 0x65, 0x63, 0x32, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x75, 0x5f, // ec2(0.0)))) - u_ - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x20, // extentRadius.z) - 0x2b, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, 0x20, // + (u_params.x * - 0x30, 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, // 0.5)) / u_params - 0x2e, 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x20, // .x, 0.0, 1.0))) - 0x2a, 0x20, 0x28, 0x5f, 0x34, 0x33, 0x36, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x32, 0x30, 0x29, 0x3b, // * (_436 * _420); - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, // . }. else. - 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // {. hi - 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x33, 0x37, 0x3b, 0x0a, 0x20, // ghp vec4 _537;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, // if (u_par - 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.w == 1.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // highp vec4 _ - 0x32, 0x31, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, // 213 = texture(s_ - 0x74, 0x65, 0x78, 0x2c, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, // tex, (u_paintMat - 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x30, 0x32, 0x29, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x75, 0x5f, // * _402).xy / u_ - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x29, // extentRadius.xy) - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // ;. hi - 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x33, 0x34, 0x3b, 0x0a, 0x20, // ghp vec4 _534;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, // if (u - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, // _params.z == 1.0 - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // ). {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x32, 0x32, 0x32, // highp float _222 - 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // = _213.w;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x34, 0x20, // _534 - 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x32, 0x31, 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x20, // = vec4(_213.xyz - 0x2a, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x2c, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x29, 0x3b, 0x0a, 0x20, // * _222, _222);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x34, // _534 - 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = _213;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, // highp vec4 _5 - 0x33, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 35;. - 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, // if (u_params.z = - 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = 2.0). - 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x34, // _535 = _534 - 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .xxxx;. - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x34, 0x3b, // _535 = _534; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // . }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x37, 0x20, // _537 - 0x3d, 0x20, 0x28, 0x5f, 0x35, 0x33, 0x35, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, // = (_535 * u_inne - 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x34, 0x33, 0x36, 0x20, 0x2a, 0x20, // rCol) * (_436 * - 0x5f, 0x34, 0x32, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // _420);. } - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // . else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // s_tex2;.uniform + 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x63, 0x6c, // highp vec4 u_cl + 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // ipBounds;.unifor + 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, // m highp sampler2 + 0x44, 0x20, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x3b, 0x0a, 0x0a, 0x69, 0x6e, 0x20, 0x68, 0x69, // D s_clip;..in hi + 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, // ghp vec2 v_posit + 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x69, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, // ion;.in highp ve + 0x63, 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, // c2 v_texcoord0;. + 0x69, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, // in highp vec2 v_ + 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, // texcoord1;.layou + 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, // t(location = 0) + 0x6f, 0x75, 0x74, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x62, // out highp vec4 b + 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, // gfx_FragColor;.. + 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, // void main().{. + 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x34, 0x34, // highp vec3 _44 + 0x39, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, // 9 = vec3(v_posit + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, // ion, 1.0);. h + 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x34, 0x36, 0x30, 0x20, 0x3d, // ighp vec2 _460 = + 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x28, 0x61, // vec2(0.5) - ((a + 0x62, 0x73, 0x28, 0x28, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, // bs((u_scissorMat + 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x75, // * _449).xy) - u + 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, // _scissorExtScale + 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .xy) * u_scissor + 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x7a, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, // ExtScale.zw);. + 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, // highp float _4 + 0x36, 0x37, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x36, 0x30, 0x2e, // 67 = clamp(_460. + 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x63, // x, 0.0, 1.0) * c + 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x36, 0x30, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, // lamp(_460.y, 0.0 + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, // , 1.0);. high + 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x6d, // p float _483 = m + 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x61, // in(1.0, (1.0 - a + 0x62, 0x73, 0x28, 0x28, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, // bs((v_texcoord0. + 0x78, 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, // x * 2.0) - 1.0)) + 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x20, 0x2a, // * u_params.y) * + 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, // min(1.0, v_texc + 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // oord0.y);. hi + 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x3b, 0x0a, 0x20, // ghp vec4 _588;. + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // if (u_params. + 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // w == 0.0). {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, // highp ve + 0x63, 0x32, 0x20, 0x5f, 0x34, 0x39, 0x35, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x75, // c2 _495 = abs((u + 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, // _paintMat * _449 + 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, // ).xy) - (u_exten + 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x20, 0x2d, 0x20, 0x76, 0x65, 0x63, // tRadius.xy - vec + 0x32, 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // 2(u_extentRadius + 0x2e, 0x7a, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, // .z));. _5 + 0x38, 0x38, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // 88 = mix(u_inner + 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, // Col, u_outerCol, + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x28, 0x6d, // vec4(clamp((((m + 0x69, 0x6e, 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x39, 0x35, 0x2e, 0x78, 0x2c, 0x20, 0x5f, // in(max(_495.x, _ + 0x34, 0x39, 0x35, 0x2e, 0x79, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x6c, // 495.y), 0.0) + l + 0x65, 0x6e, 0x67, 0x74, 0x68, 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x39, 0x35, 0x2c, 0x20, // ength(max(_495, + 0x76, 0x65, 0x63, 0x32, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x75, // vec2(0.0)))) - u + 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, // _extentRadius.z) + 0x20, 0x2b, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, // + (u_params.x * + 0x20, 0x30, 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, // 0.5)) / u_param + 0x73, 0x2e, 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, // s.x, 0.0, 1.0))) + 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, // * (_483 * _467) + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, // ;. }. else + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, // . {. h + 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x39, 0x3b, 0x0a, // ighp vec4 _589;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, // if (u_pa + 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, // rams.w == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // highp vec4 + 0x5f, 0x32, 0x31, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, // _213 = texture(s + 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, // _tex, (u_paintMa + 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x29, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x75, // t * _449).xy / u + 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, // _extentRadius.xy + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, // );. h + 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x35, 0x3b, 0x0a, // ighp vec4 _585;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, // u_params.z == 1. + 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // 0). { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x32, 0x32, // highp float _22 + 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 2 = _213.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x35, // _585 + 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x32, 0x31, 0x33, 0x2e, 0x78, 0x79, 0x7a, // = vec4(_213.xyz + 0x20, 0x2a, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x2c, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x29, 0x3b, 0x0a, // * _222, _222);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, // _58 + 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 5 = _213;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // highp vec4 _ - 0x35, 0x33, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 538;. - 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, // if (u_params.w + 0x35, 0x38, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 586;. + 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, // if (u_params.z 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 2.0). 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x38, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, // _538 = vec - 0x34, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4(1.0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, // _586 = _58 + 0x35, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 5.xxxx;. 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x35, // _586 = _585 + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // ;. }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x39, // _589 + 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // = (_586 * u_inn + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x2a, // erCol) * (_483 * + 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _467);. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // }. else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // highp vec4 - 0x5f, 0x35, 0x33, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _539;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // if (u_para - 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, // ms.w == 3.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x32, // highp vec4 _2 - 0x36, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, // 63 = texture(s_t - 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, // ex, v_texcoord0) - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. + 0x5f, 0x35, 0x39, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _590;. + 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, // if (u_params.w + 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 2.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, // _590 = ve + 0x63, 0x34, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // c4(1.0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, // highp vec4 - 0x20, 0x5f, 0x35, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _532;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, // if (u - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, // _params.z == 1.0 - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x5f, 0x35, 0x39, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _591;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, // if (u_par + 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.w == 3.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x32, 0x37, 0x32, // highp float _272 - 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x36, 0x33, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // = _263.w;. + 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // highp vec4 _ + 0x32, 0x36, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, // 263 = texture(s_ + 0x74, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, // tex, v_texcoord0 + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // );. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, // highp vec + 0x34, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _583;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, // u_params.z == 1. + 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, // _532 = vec4(_ - 0x32, 0x36, 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x32, 0x37, 0x32, 0x2c, 0x20, // 263.xyz * _272, - 0x5f, 0x32, 0x37, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _272);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. + 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x32, 0x37, // highp float _27 + 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x36, 0x33, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 2 = _263.w;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, // _583 = vec4( + 0x5f, 0x32, 0x36, 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x32, 0x37, 0x32, 0x2c, // _263.xyz * _272, + 0x20, 0x5f, 0x32, 0x37, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _272);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x36, 0x33, 0x3b, // _532 = _263; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, // highp - 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x33, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // vec4 _533;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, // if (u_params.z = - 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = 2.0). + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x33, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, // _533 = vec4 - 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x33, 0x32, 0x2e, 0x78, 0x20, // (clamp(((_532.x - 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, // - u_sdf.x) / u_s - 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, // df.z) + 0.5, 0.0 - 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, // , 1.0) * (1.0 - - 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x33, 0x32, 0x2e, 0x78, 0x20, 0x2d, // clamp(((_532.x - - 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, // u_sdf.y) / u_sd - 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // f.z) + 0.5, 0.0, - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1.0)));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x36, 0x33, // _583 = _263 + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, // high + 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, // p vec4 _584;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, // if (u_params.z + 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 2.0). 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, // _533 = _53 - 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 2;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, // _53 - 0x39, 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x35, 0x33, 0x33, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x32, 0x30, // 9 = (_533 * _420 - 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, // ) * u_innerCol;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, // hig - 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x34, 0x30, 0x3b, 0x0a, 0x20, 0x20, // hp vec4 _540;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, // _584 = vec + 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x33, 0x2e, 0x78, // 4(clamp(((_583.x + 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, // - u_sdf.x) / u_ + 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, // sdf.z) + 0.5, 0. + 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, // 0, 1.0) * (1.0 - + 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x33, 0x2e, 0x78, 0x20, // clamp(((_583.x + 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, // - u_sdf.y) / u_s + 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, // df.z) + 0.5, 0.0 + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // , 1.0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, // if (u_params.w - 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 4.0). + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, // highp vec - 0x34, 0x20, 0x5f, 0x33, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, // 4 _318 = texture - 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, // (s_tex, v_texcoo - 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // rd0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, // _584 = _5 + 0x38, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 83;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, // _5 + 0x39, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x36, // 91 = (_584 * _46 + 0x37, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, // 7) * u_innerCol; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // hi - 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x32, 0x36, 0x20, 0x3d, 0x20, // ghp vec4 _326 = - 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2c, 0x20, // texture(s_tex2, - 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, // v_texcoord1);. + 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x3b, 0x0a, 0x20, // ghp vec4 _592;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, // highp vec4 - 0x20, 0x5f, 0x35, 0x33, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _530;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, // i - 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, // f (u_params.z == - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1.0). + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // if (u_params. + 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // w == 4.0). 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, // high - 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x20, 0x3d, 0x20, 0x5f, // p float _335 = _ - 0x33, 0x31, 0x38, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 318.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, // highp ve + 0x63, 0x34, 0x20, 0x5f, 0x33, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, // c4 _318 = textur + 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // e(s_tex, v_texco + 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ord0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, // h + 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x32, 0x36, 0x20, 0x3d, // ighp vec4 _326 = + 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2c, // texture(s_tex2, + 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x29, 0x3b, 0x0a, 0x20, // v_texcoord1);. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, // _530 = vec4(_ - 0x33, 0x31, 0x38, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x2c, 0x20, // 318.xyz * _335, - 0x5f, 0x33, 0x33, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _335);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, // highp vec + 0x34, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _581;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, // if (u_params.z = + 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // { 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, // hig + 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x20, 0x3d, 0x20, // hp float _335 = + 0x5f, 0x33, 0x31, 0x38, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _318.w;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, // _581 = vec4( + 0x5f, 0x33, 0x31, 0x38, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x2c, // _318.xyz * _335, + 0x20, 0x5f, 0x33, 0x33, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _335);. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x31, 0x38, 0x3b, // _530 = _318; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // else. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, // highp vec4 _5 - 0x33, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 31;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( - 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, // u_params.z == 2. - 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x31, 0x20, 0x3d, 0x20, // _531 = - 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x33, // vec4(clamp(((_53 - 0x30, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, // 0.x - u_sdf.x) / - 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, // u_sdf.z) + 0.5, - 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, // 0.0, 1.0) * (1. - 0x30, 0x20, 0x2d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x33, 0x30, // 0 - clamp(((_530 - 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, // .x - u_sdf.y) / - 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, // u_sdf.z) + 0.5, - 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // 0.0, 1.0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x31, 0x38, // _581 = _318 + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, // e - 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. + 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // highp vec4 _ + 0x35, 0x38, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 582;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // if + 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, // (u_params.z == 2 + 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .0). 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x31, 0x20, 0x3d, // _531 = - 0x20, 0x5f, 0x35, 0x33, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _530;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x34, 0x30, 0x20, 0x3d, 0x20, // _540 = - 0x28, 0x28, 0x5f, 0x35, 0x33, 0x31, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x32, 0x30, 0x29, 0x20, 0x2a, // ((_531 * _420) * - 0x20, 0x5f, 0x33, 0x32, 0x36, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // _326) * u_inner - 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // Col;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, // e - 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x5f, 0x35, 0x34, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x34, 0x31, 0x3b, 0x0a, 0x20, // _540 = _541;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x32, 0x20, 0x3d, // _582 = + 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, // vec4(clamp(((_5 + 0x38, 0x31, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, // 81.x - u_sdf.x) + 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, // / u_sdf.z) + 0.5 + 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, // , 0.0, 1.0) * (1 + 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, // .0 - clamp(((_58 + 0x31, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, // 1.x - u_sdf.y) / + 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, // u_sdf.z) + 0.5, + 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, // 0.0, 1.0)));. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x39, 0x20, 0x3d, 0x20, // _539 = - 0x5f, 0x35, 0x34, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _540;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x33, 0x38, 0x20, 0x3d, 0x20, 0x5f, // _538 = _ - 0x35, 0x33, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 539;. - 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // }. _ - 0x35, 0x33, 0x37, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 537 = _538;. - 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, // }. _5 - 0x33, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, // 36 = _537;. } - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, // . bgfx_FragCo - 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x33, 0x36, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, // lor = _536;.}... + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x32, 0x20, // _582 + 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = _581;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x20, 0x3d, // _592 = + 0x20, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x32, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x20, // ((_582 * _467) + 0x2a, 0x20, 0x5f, 0x33, 0x32, 0x36, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, // * _326) * u_inne + 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // rCol;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x33, 0x3b, 0x0a, // _592 = _593;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x31, 0x20, 0x3d, // _591 = + 0x20, 0x5f, 0x35, 0x39, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _592;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x30, 0x20, 0x3d, 0x20, // _590 = + 0x5f, 0x35, 0x39, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _591;. + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x5f, 0x35, 0x38, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, // _589 = _590;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // }. _ + 0x35, 0x38, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 588 = _589;. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x33, 0x37, 0x37, 0x20, // }. bool _377 + 0x3d, 0x20, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x7a, // = u_clipBounds.z + 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x6f, 0x6c, // > 0.0;. bool + 0x20, 0x5f, 0x33, 0x38, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, // _383;. if (_ + 0x33, 0x37, 0x37, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 377). {. + 0x20, 0x20, 0x20, 0x5f, 0x33, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // _383 = u_para + 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x21, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, // ms.w != 2.0;. + 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. else. + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x33, 0x38, 0x33, 0x20, 0x3d, // {. _383 = + 0x20, 0x5f, 0x33, 0x37, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // _377;. }. + 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x34, // highp vec4 _594 + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x33, 0x38, 0x33, 0x29, 0x0a, // ;. if (_383). + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, // {. hi + 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x33, 0x39, 0x33, 0x20, 0x3d, 0x20, // ghp vec2 _393 = + 0x28, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x20, 0x75, 0x5f, // (v_position - u_ + 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2f, // clipBounds.xy) / + 0x20, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x7a, 0x77, // u_clipBounds.zw + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, // ;. highp + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x39, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x39, // float _396 = _39 + 0x33, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, // 3.x;. hig + 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x39, 0x39, 0x20, 0x3d, 0x20, // hp float _399 = + 0x5f, 0x33, 0x39, 0x33, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _393.y;. + 0x5f, 0x35, 0x39, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x20, 0x2a, 0x20, 0x28, 0x74, // _594 = _588 * (t + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x2c, 0x20, 0x5f, // exture(s_clip, _ + 0x33, 0x39, 0x33, 0x29, 0x2e, 0x77, 0x20, 0x2a, 0x20, 0x28, 0x28, 0x28, 0x73, 0x74, 0x65, 0x70, // 393).w * (((step + 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x36, 0x29, 0x20, 0x2a, 0x20, 0x73, 0x74, // (0.0, _396) * st + 0x65, 0x70, 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x39, 0x29, 0x29, 0x20, 0x2a, // ep(0.0, _399)) * + 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, 0x31, 0x2e, 0x30, // (1.0 - step(1.0 + 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x36, 0x29, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, // , _396))) * (1.0 + 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, // - step(1.0, _39 + 0x39, 0x29, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // 9))));. }. + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // else. {. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x3b, // _594 = _588; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, // . }. bgfx_ + 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x34, // FragColor = _594 + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, // ;.}... }; diff --git a/Polyfills/Canvas/Source/Shaders/fs_nanovg_fill.sc b/Polyfills/Canvas/Source/Shaders/fs_nanovg_fill.sc index a0af56d4c..18965dac7 100644 --- a/Polyfills/Canvas/Source/Shaders/fs_nanovg_fill.sc +++ b/Polyfills/Canvas/Source/Shaders/fs_nanovg_fill.sc @@ -12,9 +12,11 @@ uniform vec4 u_scissorExtScale; uniform vec4 u_extentRadius; uniform vec4 u_params; uniform vec4 u_sdf; +uniform vec4 u_clipBounds; SAMPLER2D(s_tex, 0); SAMPLER2D(s_tex2, 1); +SAMPLER2D(s_clip, 2); #define u_scissorExt (u_scissorExtScale.xy) #define u_scissorScale (u_scissorExtScale.zw) @@ -120,5 +122,11 @@ void main() result = color * u_innerCol; } + if (u_clipBounds.z > 0.0 && u_type != 2.0) + { + vec2 uv = (v_position - u_clipBounds.xy) / u_clipBounds.zw; + float inside = step(0.0, uv.x) * step(0.0, uv.y) * (1.0 - step(1.0, uv.x)) * (1.0 - step(1.0, uv.y)); + result *= texture2D(s_clip, uv).a * inside; + } gl_FragColor = result; } diff --git a/Polyfills/Canvas/Source/Shaders/glsl/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/glsl/fs_nanovg_fill.h index bc4cadb12..98a41e043 100644 --- a/Polyfills/Canvas/Source/Shaders/glsl/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/glsl/fs_nanovg_fill.h @@ -1,286 +1,323 @@ -static const uint8_t fs_nanovg_fill_glsl[4525] = +static const uint8_t fs_nanovg_fill_glsl[5110] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x75, // FSH............u - 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, // _scissorMat..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, // ......u_paintMat - 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ...........u_inn - 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol........... - 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, // u_outerCol...... - 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, // .....u_scissorEx - 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // tScale.......... - 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x02, // .u_extentRadius. + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .......u_scissor + 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, // Mat...........u_ + 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x02, // scissorExtScale. 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // ..........u_para 0x6d, 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, // ms...........u_s - 0x64, 0x66, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, // df...........s_t - 0x65, 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, // ex...........s_t - 0x65, 0x78, 0x32, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc3, 0x10, 0x00, // ex2............. - 0x00, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, // .in vec2 v_posit - 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x74, // ion;.in vec2 v_t - 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, // excoord0;.in vec - 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x3b, 0x0a, 0x75, // 2 v_texcoord1;.u - 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x75, 0x5f, 0x73, 0x63, // niform mat3 u_sc - 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // issorMat;.unifor - 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, // m mat3 u_paintMa - 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // t;.uniform vec4 - 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, // u_innerCol;.unif - 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, // orm vec4 u_outer - 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, // Col;.uniform vec - 0x34, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, // 4 u_scissorExtSc - 0x61, 0x6c, 0x65, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, // ale;.uniform vec - 0x34, 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // 4 u_extentRadius - 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, // ;.uniform vec4 u - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // _params;.uniform - 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x3b, 0x0a, 0x75, 0x6e, 0x69, // vec4 u_sdf;.uni - 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, // form sampler2D s - 0x5f, 0x74, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, // _tex;.uniform sa - 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x3b, 0x0a, // mpler2D s_tex2;. - 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, // void main ().{. - 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x3b, 0x0a, // vec4 result_1;. - 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, // float tmpvar_2 - 0x3b, 0x0a, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x73, 0x63, 0x5f, 0x33, 0x3b, 0x0a, 0x20, // ;. vec2 sc_3;. - 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x34, 0x3b, 0x0a, // vec3 tmpvar_4;. - 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x34, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x31, // tmpvar_4.z = 1 - 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x34, 0x2e, 0x78, // .0;. tmpvar_4.x - 0x79, 0x20, 0x3d, 0x20, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, // y = v_position;. - 0x20, 0x20, 0x73, 0x63, 0x5f, 0x33, 0x20, 0x3d, 0x20, 0x28, 0x76, 0x65, 0x63, 0x32, 0x28, 0x30, // sc_3 = (vec2(0 - 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x28, 0x0a, 0x20, 0x20, // .5, 0.5) - ((. - 0x20, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // abs((u_scissor - 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x34, 0x29, 0x2e, // Mat * tmpvar_4). - 0x78, 0x79, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, // xy). - u_sciss - 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2a, // orExtScale.xy) * - 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, // u_scissorExtSca - 0x6c, 0x65, 0x2e, 0x7a, 0x77, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // le.zw));. tmpva - 0x72, 0x5f, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x20, 0x28, 0x73, 0x63, // r_2 = (clamp (sc - 0x5f, 0x33, 0x2e, 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, // _3.x, 0.0, 1.0) - 0x2a, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x20, 0x28, 0x73, 0x63, 0x5f, 0x33, 0x2e, 0x79, 0x2c, // * clamp (sc_3.y, - 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x66, // 0.0, 1.0));. f - 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x35, 0x3b, 0x0a, 0x20, // loat tmpvar_5;. - 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x35, 0x20, 0x3d, 0x20, 0x28, 0x6d, 0x69, 0x6e, // tmpvar_5 = (min - 0x20, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x28, 0x31, 0x2e, // (1.0, (. (1. - 0x30, 0x20, 0x2d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x28, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, // 0 - abs(((v_texc - 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x20, 0x2d, // oord0.x * 2.0) - - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x70, // 1.0))). * u_p - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x6d, 0x69, 0x6e, 0x20, // arams.y)) * min - 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, // (1.0, v_texcoord - 0x30, 0x2e, 0x79, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, // 0.y));. if ((u_ - 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x29, // params.w == 0.0) - 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x74, 0x6d, 0x70, // ) {. vec3 tmp - 0x76, 0x61, 0x72, 0x5f, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // var_6;. tmpva - 0x72, 0x5f, 0x36, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, // r_6.z = 1.0;. - 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x36, 0x2e, 0x78, 0x79, 0x20, 0x3d, 0x20, 0x76, // tmpvar_6.xy = v - 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, // _position;. v - 0x65, 0x63, 0x32, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x37, 0x3b, 0x0a, 0x20, 0x20, // ec2 tmpvar_7;. - 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x37, 0x20, 0x3d, 0x20, 0x28, 0x61, 0x62, // tmpvar_7 = (ab - 0x73, 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, // s((u_paintMat * - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x36, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, // tmpvar_6).xy) - - 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, // (u_extentRadius. - 0x78, 0x79, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, // xy - u_extentRad - 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x7a, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, // ius.zz));. ve - 0x63, 0x32, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, // c2 tmpvar_8;. - 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x38, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x20, // tmpvar_8 = max - 0x28, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x37, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x3b, // (tmpvar_7, 0.0); - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x20, 0x3d, 0x20, // . result_1 = - 0x28, 0x6d, 0x69, 0x78, 0x20, 0x28, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, // (mix (u_innerCol - 0x2c, 0x20, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x63, 0x6c, // , u_outerCol, cl - 0x61, 0x6d, 0x70, 0x20, 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x28, // amp (. (((( - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x20, 0x28, 0x6d, 0x61, // . min (ma - 0x78, 0x20, 0x28, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x37, 0x2e, 0x78, 0x2c, 0x20, 0x74, // x (tmpvar_7.x, t - 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x37, 0x2e, 0x79, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, // mpvar_7.y), 0.0) - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2b, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // . + . - 0x20, 0x20, 0x20, 0x73, 0x71, 0x72, 0x74, 0x28, 0x64, 0x6f, 0x74, 0x20, 0x28, 0x74, 0x6d, 0x70, // sqrt(dot (tmp - 0x76, 0x61, 0x72, 0x5f, 0x38, 0x2c, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x38, 0x29, // var_8, tmpvar_8) - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x29, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x65, 0x78, // ). ) - u_ex - 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, // tentRadius.z) + - 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x30, 0x2e, // (u_params.x * 0. - 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, // 5)) / u_params.x - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, // ). , 0.0, 1.0 - 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x35, 0x20, 0x2a, // )) * (tmpvar_5 * - 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x7d, // tmpvar_2));. } - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // else {. if ( - 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x31, // (u_params.w == 1 - 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, // .0)) {. vec - 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 color_9;. - 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x30, 0x3b, // vec3 tmpvar_10; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x30, // . tmpvar_10 - 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .z = 1.0;. - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x30, 0x2e, 0x78, 0x79, 0x20, 0x3d, 0x20, 0x76, // tmpvar_10.xy = v - 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // _position;. - 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x31, 0x3b, // vec4 tmpvar_11; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x31, // . tmpvar_11 - 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x73, 0x5f, 0x74, 0x65, // = texture (s_te - 0x78, 0x2c, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, // x, ((u_paintMat - 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x30, 0x29, 0x2e, 0x78, 0x79, 0x20, // * tmpvar_10).xy - 0x2f, 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // / u_extentRadius - 0x2e, 0x78, 0x79, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, // .xy));. col - 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x31, // or_9 = tmpvar_11 - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, // ;. if ((u_p - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, // arams.z == 1.0)) - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // {. vec4 - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // tmpvar_12;. - 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x32, 0x2e, 0x78, 0x79, 0x7a, // tmpvar_12.xyz - 0x20, 0x3d, 0x20, 0x28, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x31, 0x2e, 0x78, 0x79, // = (tmpvar_11.xy - 0x7a, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x31, 0x2e, 0x77, 0x29, // z * tmpvar_11.w) - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, // ;. tmpvar - 0x5f, 0x31, 0x32, 0x2e, 0x77, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, // _12.w = tmpvar_1 - 0x31, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, // 1.w;. col - 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x32, // or_9 = tmpvar_12 - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. };. - 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, // if ((u_params.z - 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // == 2.0)) {. - 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x63, 0x6f, // color_9 = co - 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // lor_9.xxxx;. - 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, // };. color - 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x2a, 0x20, // _9 = (color_9 * - 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // u_innerCol);. - 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6f, // color_9 = (co - 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x20, 0x2a, 0x20, 0x28, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, // lor_9 * (tmpvar_ - 0x35, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x29, 0x29, 0x3b, 0x0a, // 5 * tmpvar_2));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x20, 0x3d, // result_1 = - 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, // color_9;. } - 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // else {. if - 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, // ((u_params.w == - 0x32, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 2.0)) {. - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, // result_1 = vec4( - 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x31, // 1.0, 1.0, 1.0, 1 - 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, // .0);. } els - 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // e {. if ( - 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x33, // (u_params.w == 3 - 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .0)) {. - 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x3b, 0x0a, // vec4 color_13;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x74, // vec4 t - 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // mpvar_14;. - 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x34, 0x20, 0x3d, 0x20, // tmpvar_14 = - 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, // texture (s_tex, - 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, // v_texcoord0);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, // color_13 - 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x34, 0x3b, 0x0a, 0x20, 0x20, // = tmpvar_14;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, // if ((u_p - 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, // arams.z == 1.0)) - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, // {. v - 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x35, 0x3b, 0x0a, 0x20, // ec4 tmpvar_15;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // tmpva - 0x72, 0x5f, 0x31, 0x35, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x3d, 0x20, 0x28, 0x74, 0x6d, 0x70, 0x76, // r_15.xyz = (tmpv - 0x61, 0x72, 0x5f, 0x31, 0x34, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, // ar_14.xyz * tmpv - 0x61, 0x72, 0x5f, 0x31, 0x34, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ar_14.w);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x35, 0x2e, // tmpvar_15. - 0x77, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x34, 0x2e, 0x77, 0x3b, // w = tmpvar_14.w; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, // . col - 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, // or_13 = tmpvar_1 - 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, // 5;. };. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, // if ((u - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, // _params.z == 2.0 - 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // )) {. - 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x36, // float tmpvar_16 - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, // ;. tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x36, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, // pvar_16 = (clamp - 0x20, 0x28, 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ((. - 0x20, 0x20, 0x28, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x2e, 0x78, 0x20, 0x2d, // ((color_13.x - - 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, // u_sdf.x) / u_sd - 0x66, 0x2e, 0x7a, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // f.z). - 0x20, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // + 0.5), 0.0, 1 - 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x63, 0x6c, 0x61, // .0) * (1.0 - cla - 0x6d, 0x70, 0x20, 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // mp (. - 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x2e, 0x78, // (((color_13.x - 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, // - u_sdf.y) / u_ - 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x0a, 0x20, 0x20, // sdf.z) + 0.5). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // , 0.0, - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1.0)));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, // vec4 tmpvar - 0x5f, 0x31, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _17;. - 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x37, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x74, // tmpvar_17.x = t - 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // mpvar_16;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x37, 0x2e, // tmpvar_17. - 0x79, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x36, 0x3b, 0x0a, 0x20, // y = tmpvar_16;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // tmpva - 0x72, 0x5f, 0x31, 0x37, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, // r_17.z = tmpvar_ - 0x31, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 16;. - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x37, 0x2e, 0x77, 0x20, 0x3d, 0x20, 0x74, 0x6d, // tmpvar_17.w = tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // pvar_16;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x20, 0x3d, 0x20, // color_13 = - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // tmpvar_17;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // };. - 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6f, // color_13 = (co - 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x33, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, // lor_13 * tmpvar_ - 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, // 2);. re - 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, // sult_1 = (color_ - 0x31, 0x33, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, // 13 * u_innerCol) - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, // ;. } else - 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // {. if - 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, // ((u_params.w == - 0x34, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4.0)) {. - 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, // vec4 color_1 - 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, // 8;. v - 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x39, 0x3b, 0x0a, 0x20, // ec4 tmpvar_19;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // tmpva - 0x72, 0x5f, 0x31, 0x39, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x20, 0x28, // r_19 = texture ( - 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, // s_tex, v_texcoor - 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // d0);. - 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, // color_18 = tmpv - 0x61, 0x72, 0x5f, 0x31, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ar_19;. - 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, // vec4 tmpvar_2 - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, // 0;. t - 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, // mpvar_20 = textu - 0x72, 0x65, 0x20, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, // re (s_tex2, v_te - 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // xcoord1);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, // if ((u_par - 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, // ams.z == 1.0)) { - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, // . v - 0x65, 0x63, 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x31, 0x3b, 0x0a, 0x20, // ec4 tmpvar_21;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, // tmp - 0x76, 0x61, 0x72, 0x5f, 0x32, 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x3d, 0x20, 0x28, 0x74, 0x6d, // var_21.xyz = (tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x39, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x74, 0x6d, // pvar_19.xyz * tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, 0x39, 0x2e, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // pvar_19.w);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, // tmpvar - 0x5f, 0x32, 0x31, 0x2e, 0x77, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x31, // _21.w = tmpvar_1 - 0x39, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 9.w;. - 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x74, 0x6d, // color_18 = tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // pvar_21;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // };. - 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, // if ((u_param - 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, // s.z == 2.0)) {. + 0x64, 0x66, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, // df...........u_p + 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x03, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, // aintMat......... + 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // ..u_extentRadius + 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ...........u_inn + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol........... + 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, // u_outerCol...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // .....u_clipBound + 0x73, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // s...........s_te + 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, // x...........s_te + 0x78, 0x32, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x63, // x2...........s_c + 0x6c, 0x69, 0x70, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0xe4, 0x12, 0x00, // lip............. + 0x00, 0x0a, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x33, 0x3b, 0x0a, 0x0a, 0x75, 0x6e, // ..vec4 _593;..un + 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x33, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, // iform mat3 u_sci + 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // ssorMat;.uniform + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, // vec4 u_scissorE + 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, // xtScale;.uniform + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3b, 0x0a, // vec4 u_params;. + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x73, // uniform vec4 u_s + 0x64, 0x66, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x33, // df;.uniform mat3 + 0x20, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x3b, 0x0a, 0x75, 0x6e, 0x69, // u_paintMat;.uni + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, // form vec4 u_exte + 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // ntRadius;.unifor + 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, // m vec4 u_innerCo + 0x6c, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // l;.uniform vec4 + 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, // u_outerCol;.unif + 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x5f, // orm sampler2D s_ + 0x74, 0x65, 0x78, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, // tex;.uniform sam + 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x3b, 0x0a, 0x75, // pler2D s_tex2;.u + 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, 0x5f, 0x63, 0x6c, // niform vec4 u_cl + 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x3b, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, // ipBounds;.unifor + 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x5f, 0x63, 0x6c, // m sampler2D s_cl + 0x69, 0x70, 0x3b, 0x0a, 0x0a, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x70, // ip;..in vec2 v_p + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, // osition;.in vec2 + 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x69, 0x6e, // v_texcoord0;.in + 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, // vec2 v_texcoord + 0x31, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, // 1;.layout(locati + 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, // on = 0) out vec4 + 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, // bgfx_FragColor; + 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, // ..void main().{. + 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x33, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x20, 0x3d, 0x20, // vec3 _449 = + 0x76, 0x65, 0x63, 0x33, 0x28, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, // vec3(v_position, + 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, // 1.0);. vec2 + 0x5f, 0x34, 0x36, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, // _460 = vec2(0.5) + 0x20, 0x2d, 0x20, 0x28, 0x28, 0x61, 0x62, 0x73, 0x28, 0x28, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, // - ((abs((u_scis + 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x29, 0x2e, 0x78, // sorMat * _449).x + 0x79, 0x29, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, // y) - u_scissorEx + 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x73, // tScale.xy) * u_s + 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x7a, // cissorExtScale.z + 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x34, // w);. float _4 + 0x36, 0x37, 0x20, 0x3d, 0x20, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x36, 0x30, 0x2e, // 67 = clamp(_460. + 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x63, // x, 0.0, 1.0) * c + 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x34, 0x36, 0x30, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, // lamp(_460.y, 0.0 + 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // , 1.0);. floa + 0x74, 0x20, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, // t _483 = min(1.0 + 0x2c, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x76, 0x5f, // , (1.0 - abs((v_ + 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x32, 0x2e, // texcoord0.x * 2. + 0x30, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x70, // 0) - 1.0)) * u_p + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x6d, 0x69, 0x6e, 0x28, 0x31, // arams.y) * min(1 + 0x2e, 0x30, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, // .0, v_texcoord0. + 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, // y);. vec4 _58 + 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, // 8;. if (u_par + 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.w == 0.0). + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, // {. vec2 + 0x20, 0x5f, 0x34, 0x39, 0x35, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x75, 0x5f, 0x70, // _495 = abs((u_p + 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x29, 0x2e, // aintMat * _449). + 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, // xy) - (u_extentR + 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x20, 0x2d, 0x20, 0x76, 0x65, 0x63, 0x32, 0x28, // adius.xy - vec2( + 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, // u_extentRadius.z + 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x38, // ));. _588 + 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, // = mix(u_innerCo + 0x6c, 0x2c, 0x20, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x76, // l, u_outerCol, v + 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x28, 0x6d, 0x69, 0x6e, // ec4(clamp((((min + 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x39, 0x35, 0x2e, 0x78, 0x2c, 0x20, 0x5f, 0x34, 0x39, // (max(_495.x, _49 + 0x35, 0x2e, 0x79, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x65, 0x6e, // 5.y), 0.0) + len + 0x67, 0x74, 0x68, 0x28, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x34, 0x39, 0x35, 0x2c, 0x20, 0x76, 0x65, // gth(max(_495, ve + 0x63, 0x32, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x65, // c2(0.0)))) - u_e + 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x20, 0x2b, // xtentRadius.z) + + 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x30, // (u_params.x * 0 + 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // .5)) / u_params. + 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x20, 0x2a, // x, 0.0, 1.0))) * + 0x20, 0x28, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x3b, 0x0a, // (_483 * _467);. + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // }. else. + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, // {. vec + 0x34, 0x20, 0x5f, 0x35, 0x38, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _589;. + 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, // if (u_params.w = + 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // = 1.0). { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, // . vec + 0x34, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, // 4 _213 = texture + 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, // (s_tex, (u_paint + 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x34, 0x39, 0x29, 0x2e, 0x78, 0x79, 0x20, 0x2f, // Mat * _449).xy / + 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, // u_extentRadius. + 0x78, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // xy);. + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // vec4 _585;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, // if (u_pa + 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, // rams.z == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // flo - 0x61, 0x74, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, // at tmpvar_22;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, // tmpv - 0x61, 0x72, 0x5f, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x20, 0x28, // ar_22 = (clamp ( - 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // (. - 0x20, 0x20, 0x28, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x2e, 0x78, 0x20, 0x2d, // ((color_18.x - - 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, // u_sdf.x) / u_sd - 0x66, 0x2e, 0x7a, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // f.z). - 0x20, 0x20, 0x20, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // + 0.5), 0.0, - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x63, // 1.0) * (1.0 - c - 0x6c, 0x61, 0x6d, 0x70, 0x20, 0x28, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lamp (. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, // (((color_ - 0x31, 0x38, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, // 18.x - u_sdf.y) - 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, // / u_sdf.z) + 0.5 - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ). - 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, // , 0.0, 1.0)));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, // vec - 0x34, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, // 4 tmpvar_23;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, // tmpva - 0x72, 0x5f, 0x32, 0x33, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, // r_23.x = tmpvar_ - 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 22;. - 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x33, 0x2e, 0x79, 0x20, 0x3d, 0x20, // tmpvar_23.y = - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // tmpvar_22;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, // tmpvar_ - 0x32, 0x33, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x32, // 23.z = tmpvar_22 + 0x61, 0x74, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x2e, 0x77, // at _222 = _213.w 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. - 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x33, 0x2e, 0x77, 0x20, 0x3d, 0x20, 0x74, 0x6d, // tmpvar_23.w = tm - 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // pvar_22;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, // color_18 - 0x3d, 0x20, 0x74, 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, // = tmpvar_23;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // };. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, // color_18 - 0x20, 0x3d, 0x20, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x2a, 0x20, 0x74, // = (color_18 * t - 0x6d, 0x70, 0x76, 0x61, 0x72, 0x5f, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // mpvar_2);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x3d, // color_18 = - 0x20, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x2a, 0x20, 0x74, 0x6d, 0x70, // (color_18 * tmp - 0x76, 0x61, 0x72, 0x5f, 0x32, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // var_20);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x20, 0x3d, 0x20, // result_1 = - 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x5f, 0x31, 0x38, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, // (color_18 * u_in - 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // nerCol);. - 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, // };. }; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x3b, // . };. }; - 0x0a, 0x20, 0x20, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x67, 0x6c, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, // . };. gl_FragC - 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x5f, 0x31, 0x3b, // olor = result_1; - 0x0a, 0x7d, 0x0a, 0x0a, 0x00, // .}... + 0x20, 0x20, 0x5f, 0x35, 0x38, 0x35, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x32, // _585 = vec4(_2 + 0x31, 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x32, 0x32, 0x32, 0x2c, 0x20, 0x5f, // 13.xyz * _222, _ + 0x32, 0x32, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 222);. + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x31, 0x33, 0x3b, 0x0a, // _585 = _213;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // vec4 _ + 0x35, 0x38, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 586;. + 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, // if (u_params.z + 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 2.0). + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, // _586 = _58 + 0x35, 0x2e, 0x78, 0x78, 0x78, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 5.xxxx;. + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x35, // _586 = _585 + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // ;. }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x39, // _589 + 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x35, 0x38, 0x36, 0x20, 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // = (_586 * u_inn + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x34, 0x38, 0x33, 0x20, 0x2a, // erCol) * (_483 * + 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _467);. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // }. else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x30, 0x3b, 0x0a, // vec4 _590;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, // u_params.w == 2. + 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // 0). { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x5f, 0x35, 0x39, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x31, 0x2e, 0x30, // _590 = vec4(1.0 + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // );. } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, // . els + 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // e. {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x39, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // vec4 _591;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, // if (u + 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x2e, 0x30, // _params.w == 3.0 + 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ). + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x32, 0x36, // vec4 _26 + 0x33, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, // 3 = texture(s_te + 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, // x, v_texcoord0); + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x3b, 0x0a, // vec4 _583;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, // if (u_params + 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // .z == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, // float _ + 0x32, 0x37, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x36, 0x33, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, // 272 = _263.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, // _583 = vec + 0x34, 0x28, 0x5f, 0x32, 0x36, 0x33, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x32, 0x37, // 4(_263.xyz * _27 + 0x32, 0x2c, 0x20, 0x5f, 0x32, 0x37, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 2, _272);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x32, // _583 = _2 + 0x36, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 63;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, // ve + 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // c4 _584;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // if + 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, // (u_params.z == 2 + 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, // _584 = vec4(cl + 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x33, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, // amp(((_583.x - u + 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, // _sdf.x) / u_sdf. + 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // z) + 0.5, 0.0, 1 + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x63, 0x6c, 0x61, // .0) * (1.0 - cla + 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x33, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, // mp(((_583.x - u_ + 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, // sdf.y) / u_sdf.z + 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, // ) + 0.5, 0.0, 1. + 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x33, 0x3b, 0x0a, // _584 = _583;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x31, 0x20, 0x3d, // _591 = + 0x20, 0x28, 0x5f, 0x35, 0x38, 0x34, 0x20, 0x2a, 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x20, 0x2a, // (_584 * _467) * + 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, // u_innerCol;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, // e + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, // vec4 _ + 0x35, 0x39, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 592;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, // if (u_p + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x34, 0x2e, 0x30, 0x29, 0x0a, // arams.w == 4.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, // ve + 0x63, 0x34, 0x20, 0x5f, 0x33, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, // c4 _318 = textur + 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // e(s_tex, v_texco + 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ord0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, // v + 0x65, 0x63, 0x34, 0x20, 0x5f, 0x33, 0x32, 0x36, 0x20, 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, // ec4 _326 = textu + 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2c, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, // re(s_tex2, v_tex + 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // coord1);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // vec4 _581;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, // if (u_params + 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // .z == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x33, // float _335 = _3 + 0x31, 0x38, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 18.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x33, // _581 = vec4(_3 + 0x31, 0x38, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x33, 0x33, 0x35, 0x2c, 0x20, 0x5f, // 18.xyz * _335, _ + 0x33, 0x33, 0x35, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 335);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x31, 0x38, 0x3b, 0x0a, // _581 = _318;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x38, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, // vec4 _582;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, // if (u_param + 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, // s.z == 2.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x35, 0x38, 0x32, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x28, 0x63, 0x6c, // _582 = vec4(cl + 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x31, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, // amp(((_581.x - u + 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, // _sdf.x) / u_sdf. + 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // z) + 0.5, 0.0, 1 + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x63, 0x6c, 0x61, // .0) * (1.0 - cla + 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x31, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x75, 0x5f, // mp(((_581.x - u_ + 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, // sdf.y) / u_sdf.z + 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, // ) + 0.5, 0.0, 1. + 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x31, 0x3b, 0x0a, // _582 = _581;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x5f, 0x35, 0x38, 0x32, 0x20, // _592 = ((_582 + 0x2a, 0x20, 0x5f, 0x34, 0x36, 0x37, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x33, 0x32, 0x36, 0x29, 0x20, // * _467) * _326) + 0x2a, 0x20, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, // * u_innerCol;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x20, // _592 + 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = _593;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x35, 0x39, 0x31, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x32, 0x3b, 0x0a, 0x20, // _591 = _592;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x5f, 0x35, 0x39, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x31, 0x3b, 0x0a, 0x20, 0x20, // _590 = _591;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x39, 0x20, 0x3d, 0x20, 0x5f, // _589 = _ + 0x35, 0x39, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // 590;. }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x35, // _588 = _5 + 0x38, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, // 89;. }. bo + 0x6f, 0x6c, 0x20, 0x5f, 0x33, 0x37, 0x37, 0x20, 0x3d, 0x20, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, // ol _377 = u_clip + 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x7a, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x3b, 0x0a, // Bounds.z > 0.0;. + 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x5f, 0x33, 0x38, 0x33, 0x3b, 0x0a, 0x20, // bool _383;. + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x33, 0x37, 0x37, 0x29, 0x0a, 0x20, 0x20, 0x20, // if (_377). + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x33, 0x38, 0x33, 0x20, // {. _383 + 0x3d, 0x20, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x21, 0x3d, 0x20, // = u_params.w != + 0x32, 0x2e, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, // 2.0;. }. e + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. {. + 0x20, 0x20, 0x5f, 0x33, 0x38, 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x37, 0x37, 0x3b, 0x0a, 0x20, // _383 = _377;. + 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, // }. vec4 _5 + 0x39, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x33, 0x38, 0x33, // 94;. if (_383 + 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ). {. + 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x33, 0x39, 0x33, 0x20, 0x3d, 0x20, 0x28, 0x76, 0x5f, 0x70, // vec2 _393 = (v_p + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x2d, 0x20, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, // osition - u_clip + 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x75, 0x5f, 0x63, // Bounds.xy) / u_c + 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x7a, 0x77, 0x3b, 0x0a, 0x20, 0x20, // lipBounds.zw;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x39, 0x36, // float _396 + 0x20, 0x3d, 0x20, 0x5f, 0x33, 0x39, 0x33, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // = _393.x;. + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x33, 0x39, 0x39, 0x20, 0x3d, 0x20, // float _399 = + 0x5f, 0x33, 0x39, 0x33, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _393.y;. + 0x5f, 0x35, 0x39, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x20, 0x2a, 0x20, 0x28, 0x74, // _594 = _588 * (t + 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x2c, 0x20, 0x5f, // exture(s_clip, _ + 0x33, 0x39, 0x33, 0x29, 0x2e, 0x77, 0x20, 0x2a, 0x20, 0x28, 0x28, 0x28, 0x73, 0x74, 0x65, 0x70, // 393).w * (((step + 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x36, 0x29, 0x20, 0x2a, 0x20, 0x73, 0x74, // (0.0, _396) * st + 0x65, 0x70, 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x39, 0x29, 0x29, 0x20, 0x2a, // ep(0.0, _399)) * + 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, 0x31, 0x2e, 0x30, // (1.0 - step(1.0 + 0x2c, 0x20, 0x5f, 0x33, 0x39, 0x36, 0x29, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, // , _396))) * (1.0 + 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x33, 0x39, // - step(1.0, _39 + 0x39, 0x29, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // 9))));. }. + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // else. {. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x35, 0x39, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x38, 0x38, 0x3b, // _594 = _588; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, // . }. bgfx_ + 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x39, 0x34, // FragColor = _594 + 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, // ;.}... }; diff --git a/Polyfills/Canvas/Source/Shaders/metal/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/metal/fs_nanovg_fill.h index 82a3abe76..539b23be1 100644 --- a/Polyfills/Canvas/Source/Shaders/metal/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/metal/fs_nanovg_fill.h @@ -1,25 +1,31 @@ -static const uint8_t fs_nanovg_fill_mtl[5508] = +static const uint8_t fs_nanovg_fill_mtl[6200] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0c, 0x73, // FSH............s - 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, // _texSampler..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x74, 0x75, // ......s_texTextu - 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x74, // re...........s_t - 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, // ex2Sampler...... - 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x54, 0x65, 0x78, 0x74, 0x75, // .....s_tex2Textu - 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x73, // re...........u_s - 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x13, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, // cissorMat....... - 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, // ....u_scissorExt - 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x01, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // Scale........... - 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x01, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x00, // u_params........ - 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x12, 0x01, 0xb0, 0x00, 0x01, 0x00, 0x00, 0x00, // ...u_sdf........ - 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, 0x01, 0x30, // ...u_paintMat..0 - 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, // ........u_extent - 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x01, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Radius.......... - 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x60, 0x00, 0x01, // .u_innerCol..`.. - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, // ......u_outerCol - 0x12, 0x01, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, 0x78, // ..p........s_tex - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, 0x78, // ...........s_tex - 0x32, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x14, 0x00, 0x00, 0x23, // 2..........9...# + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x0c, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, // .......s_texSamp + 0x6c, 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x5f, // ler...........s_ + 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, // texTexture...... + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, // .....s_tex2Sampl + 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x74, // er...........s_t + 0x65, 0x78, 0x32, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, // ex2Texture...... + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x53, 0x61, 0x6d, 0x70, 0x6c, // .....s_clipSampl + 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x73, 0x5f, 0x63, // er...........s_c + 0x6c, 0x69, 0x70, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, // lipTexture...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, // .....u_scissorMa + 0x74, 0x13, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, // t...........u_sc + 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x01, 0x80, // issorExtScale... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, // ........u_params + 0x12, 0x01, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, // ...........u_sdf + 0x12, 0x01, 0xb0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, // ...........u_pai + 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, 0x01, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, // ntMat..0........ + 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x01, // u_extentRadius.. + 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, // .........u_inner + 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, // Col..`........u_ + 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, // outerCol..p..... + 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x12, // ...u_clipBounds. + 0x01, 0xc0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x10, // ..........s_tex. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, // ..........s_tex2 + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x73, 0x5f, 0x63, 0x6c, 0x69, // ...........s_cli + 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x95, 0x16, 0x00, 0x00, 0x23, // p..............# 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x5f, 0x73, // include .#include 0x3c, 0x73, 0x69, 0x6d, 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, 0x75, // ..u @@ -37,311 +43,349 @@ static const uint8_t fs_nanovg_fill_mtl[5508] = 0x74, 0x34, 0x20, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, // t4 u_extentRadiu 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, // s;. float4 u_ 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // params;. floa - 0x74, 0x34, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x63, 0x6f, // t4 u_sdf;.};..co - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, // nstant float4 _9 - 0x37, 0x31, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, // 71 = {};..struct - 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, // xlatMtlMain_out - 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x62, 0x67, // .{. float4 bg - 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x5b, 0x5b, 0x63, // fx_FragData0 [[c - 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, // olor(0)]];.};..s - 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, // truct xlatMtlMai - 0x6e, 0x5f, 0x69, 0x6e, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // n_in.{. float - 0x32, 0x20, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x5b, 0x5b, 0x75, // 2 v_position [[u - 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x20, 0x20, // ser(locn0)]];. - 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // float2 v_texco - 0x6f, 0x72, 0x64, 0x30, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, // ord0 [[user(locn - 0x31, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, // 1)]];. float2 - 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x20, 0x5b, 0x5b, 0x75, // v_texcoord1 [[u - 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x32, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, // ser(locn2)]];.}; - 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, // ..fragment xlatM - 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, // tlMain_out xlatM - 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x28, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, // tlMain(xlatMtlMa - 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, 0x74, 0x61, 0x67, 0x65, // in_in in [[stage - 0x5f, 0x69, 0x6e, 0x5d, 0x5d, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, // _in]], constant - 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x26, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x20, // _Global& _mtl_u - 0x5b, 0x5b, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, // [[buffer(0)]], t - 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, // exture2d - 0x73, 0x5f, 0x74, 0x65, 0x78, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, // s_tex [[texture( - 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, // 0)]], texture2d< - 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x20, 0x5b, 0x5b, // float> s_tex2 [[ - 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, // texture(1)]], sa - 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, // mpler s_texSampl - 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, // er [[sampler(0)] - 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, // ], sampler s_tex - 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, // 2Sampler [[sampl - 0x65, 0x72, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x78, // er(1)]]).{. x - 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, // latMtlMain_out o - 0x75, 0x74, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // ut = {};. flo - 0x61, 0x74, 0x33, 0x20, 0x5f, 0x37, 0x37, 0x30, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // at3 _770 = float - 0x33, 0x28, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, // 3(in.v_position, - 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // 1.0);. float - 0x32, 0x20, 0x5f, 0x37, 0x38, 0x35, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, // 2 _785 = float2( - 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x28, 0x61, 0x62, 0x73, 0x28, 0x28, 0x5f, 0x6d, // 0.5) - ((abs((_m - 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, // tl_u.u_scissorMa - 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x37, 0x30, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, // t * _770).xy) - - 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // _mtl_u.u_scissor - 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x5f, // ExtScale.xy) * _ - 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, // mtl_u.u_scissorE - 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, 0x7a, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // xtScale.zw);. - 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x37, 0x39, 0x32, 0x20, 0x3d, 0x20, 0x66, 0x61, // float _792 = fa - 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x37, 0x38, 0x35, 0x2e, 0x78, // st::clamp(_785.x - 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x66, 0x61, // , 0.0, 1.0) * fa - 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x5f, 0x37, 0x38, 0x35, 0x2e, 0x79, // st::clamp(_785.y - 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // , 0.0, 1.0);. - 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x38, 0x30, 0x38, 0x20, 0x3d, 0x20, 0x66, 0x61, // float _808 = fa - 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x31, 0x2e, // st::min(1.0, (1. - 0x30, 0x20, 0x2d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, // 0 - abs((in.v_te - 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x29, // xcoord0.x * 2.0) - 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, // - 1.0)) * _mtl_ - 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x20, 0x2a, 0x20, // u.u_params.y) * - 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x69, // fast::min(1.0, i - 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x79, 0x29, // n.v_texcoord0.y) - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, // ;. float4 _96 - 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, // 6;. if (_mtl_ - 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, // u.u_params.w == - 0x30, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 0.0). {. - 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, 0x38, 0x32, 0x30, 0x20, 0x3d, // float2 _820 = - 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, // abs((_mtl_u.u_p - 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x37, 0x30, 0x29, 0x2e, // aintMat * _770). - 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // xy) - (_mtl_u.u_ - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x20, // extentRadius.xy - 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, // - float2(_mtl_u. - 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, // u_extentRadius.z - 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x36, // ));. _966 - 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // = mix(_mtl_u.u_ - 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, // innerCol, _mtl_u - 0x2e, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x66, 0x6c, 0x6f, // .u_outerCol, flo - 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, // at4(fast::clamp( - 0x28, 0x28, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, 0x28, 0x66, 0x61, 0x73, // (((fast::min(fas - 0x74, 0x3a, 0x3a, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x38, 0x32, 0x30, 0x2e, 0x78, 0x2c, 0x20, 0x5f, // t::max(_820.x, _ - 0x38, 0x32, 0x30, 0x2e, 0x79, 0x29, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x6c, // 820.y), 0.0) + l - 0x65, 0x6e, 0x67, 0x74, 0x68, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x61, 0x78, 0x28, // ength(fast::max( - 0x5f, 0x38, 0x32, 0x30, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x30, 0x2e, 0x30, // _820, float2(0.0 - 0x29, 0x29, 0x29, 0x29, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // )))) - _mtl_u.u_ - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x20, // extentRadius.z) - 0x2b, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // + (_mtl_u.u_para - 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x5f, // ms.x * 0.5)) / _ - 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, // mtl_u.u_params.x - 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x20, 0x2a, 0x20, // , 0.0, 1.0))) * - 0x28, 0x5f, 0x38, 0x30, 0x38, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x39, 0x32, 0x29, 0x3b, 0x0a, 0x20, // (_808 * _792);. - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // }. else. - 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // {. floa - 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // t4 _967;. - 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, // if (_mtl_u.u_pa - 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, // rams.w == 1.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x38, 0x34, 0x37, // float4 _847 - 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, // = s_tex.sample( - 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x28, 0x28, // s_texSampler, (( - 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, // _mtl_u.u_paintMa - 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x37, 0x30, 0x29, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x5f, // t * _770).xy / _ - 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, // mtl_u.u_extentRa - 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // dius.xy));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, // float4 _9 - 0x36, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 64;. + 0x74, 0x34, 0x20, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, // t4 u_sdf;. fl + 0x6f, 0x61, 0x74, 0x34, 0x20, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // oat4 u_clipBound + 0x73, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, // s;.};..constant + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x32, 0x20, 0x3d, 0x20, 0x7b, // float4 _1062 = { + 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, // };..struct xlatM + 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, // tlMain_out.{. + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, // float4 bgfx_Fra + 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x5b, 0x5b, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x30, // gData0 [[color(0 + 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, // )]];.};..struct + 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x0a, 0x7b, // xlatMtlMain_in.{ + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x76, 0x5f, 0x70, 0x6f, // . float2 v_po + 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, // sition [[user(lo + 0x63, 0x6e, 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // cn0)]];. floa + 0x74, 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x20, 0x5b, // t2 v_texcoord0 [ + 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, 0x63, 0x6e, 0x31, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, // [user(locn1)]];. + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, // float2 v_tex + 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, 0x6c, 0x6f, // coord1 [[user(lo + 0x63, 0x6e, 0x32, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x72, 0x61, 0x67, // cn2)]];.};..frag + 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, // ment xlatMtlMain + 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, // _out xlatMtlMain + 0x28, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x20, // (xlatMtlMain_in + 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x5d, 0x5d, 0x2c, // in [[stage_in]], + 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x5f, 0x47, 0x6c, 0x6f, 0x62, 0x61, // constant _Globa + 0x6c, 0x26, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x20, 0x5b, 0x5b, 0x62, 0x75, 0x66, 0x66, // l& _mtl_u [[buff + 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, // er(0)]], texture + 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x20, // 2d s_tex + 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, // [[texture(0)]], + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, // texture2d + 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, // s_tex2 [[textur + 0x65, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x32, // e(1)]], texture2 + 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x20, // d s_clip + 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x32, 0x29, 0x5d, 0x5d, 0x2c, 0x20, // [[texture(2)]], + 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, // sampler s_texSam + 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x28, 0x30, // pler [[sampler(0 + 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x73, 0x5f, 0x74, // )]], sampler s_t + 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, // ex2Sampler [[sam + 0x70, 0x6c, 0x65, 0x72, 0x28, 0x31, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, // pler(1)]], sampl + 0x65, 0x72, 0x20, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // er s_clipSampler + 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x28, 0x32, 0x29, 0x5d, 0x5d, 0x29, // [[sampler(2)]]) + 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, // .{. xlatMtlMa + 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, // in_out out = {}; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x33, 0x20, 0x5f, 0x38, 0x37, 0x31, // . float3 _871 + 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x33, 0x28, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x70, // = float3(in.v_p + 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, // osition, 1.0);. + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, 0x38, 0x38, 0x36, 0x20, 0x3d, // float2 _886 = + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x30, 0x2e, 0x35, 0x29, 0x20, 0x2d, 0x20, 0x28, // float2(0.5) - ( + 0x28, 0x61, 0x62, 0x73, 0x28, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, // (abs((_mtl_u.u_s + 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, 0x38, 0x37, 0x31, // cissorMat * _871 + 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, // ).xy) - _mtl_u.u + 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, // _scissorExtScale + 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // .xy) * _mtl_u.u_ + 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x2e, // scissorExtScale. + 0x7a, 0x77, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, // zw);. float _ + 0x38, 0x39, 0x33, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, // 893 = fast::clam + 0x70, 0x28, 0x5f, 0x38, 0x38, 0x36, 0x2e, 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // p(_886.x, 0.0, 1 + 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, // .0) * fast::clam + 0x70, 0x28, 0x5f, 0x38, 0x38, 0x36, 0x2e, 0x79, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // p(_886.y, 0.0, 1 + 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, // .0);. float _ + 0x39, 0x30, 0x39, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, 0x28, // 909 = fast::min( + 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x61, 0x62, 0x73, 0x28, // 1.0, (1.0 - abs( + 0x28, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, // (in.v_texcoord0. + 0x78, 0x20, 0x2a, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, // x * 2.0) - 1.0)) + 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // * _mtl_u.u_para + 0x6d, 0x73, 0x2e, 0x79, 0x29, 0x20, 0x2a, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x69, // ms.y) * fast::mi + 0x6e, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, // n(1.0, in.v_texc + 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x2e, 0x79, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, // oord0.y);. fl + 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // oat4 _1057;. 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, // if (_mtl_u.u_par - 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.z == 1.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // floa - 0x74, 0x20, 0x5f, 0x36, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x34, 0x37, 0x2e, 0x77, 0x3b, // t _620 = _847.w; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x5f, 0x39, 0x36, 0x34, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, // _964 = float4(_ - 0x38, 0x34, 0x37, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x36, 0x32, 0x30, 0x2c, 0x20, // 847.xyz * _620, - 0x5f, 0x36, 0x32, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _620);. - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.w == 0.0). + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // {. floa + 0x74, 0x32, 0x20, 0x5f, 0x39, 0x32, 0x31, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x73, 0x28, 0x28, 0x5f, // t2 _921 = abs((_ + 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, // mtl_u.u_paintMat + 0x20, 0x2a, 0x20, 0x5f, 0x38, 0x37, 0x31, 0x29, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2d, 0x20, 0x28, // * _871).xy) - ( + 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, // _mtl_u.u_extentR + 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x78, 0x79, 0x20, 0x2d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // adius.xy - float + 0x32, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, // 2(_mtl_u.u_exten + 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, // tRadius.z));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x37, 0x20, 0x3d, 0x20, 0x6d, 0x69, 0x78, // _1057 = mix + 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, // (_mtl_u.u_innerC + 0x6f, 0x6c, 0x2c, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x6f, 0x75, 0x74, // ol, _mtl_u.u_out + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, // erCol, float4(fa + 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x28, 0x66, 0x61, 0x73, // st::clamp((((fas + 0x74, 0x3a, 0x3a, 0x6d, 0x69, 0x6e, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x61, 0x78, // t::min(fast::max + 0x28, 0x5f, 0x39, 0x32, 0x31, 0x2e, 0x78, 0x2c, 0x20, 0x5f, 0x39, 0x32, 0x31, 0x2e, 0x79, 0x29, // (_921.x, _921.y) + 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x28, // , 0.0) + length( + 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x6d, 0x61, 0x78, 0x28, 0x5f, 0x39, 0x32, 0x31, 0x2c, 0x20, // fast::max(_921, + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x29, 0x20, 0x2d, // float2(0.0)))) - + 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, // _mtl_u.u_extent + 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x5f, 0x6d, 0x74, // Radius.z) + (_mt + 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x20, 0x2a, // l_u.u_params.x * + 0x20, 0x30, 0x2e, 0x35, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, // 0.5)) / _mtl_u. + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x78, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // u_params.x, 0.0, + 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x39, 0x30, 0x39, 0x20, // 1.0))) * (_909 + 0x2a, 0x20, 0x5f, 0x38, 0x39, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // * _893);. }. + 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // else. {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, // float4 _10 + 0x35, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // 58;. if ( + 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // _mtl_u.u_params. + 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // w == 1.0). 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x34, 0x37, 0x3b, // _964 = _847; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // . }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // float - 0x34, 0x20, 0x5f, 0x39, 0x36, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _965;. - 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, // if (_mtl_u.u - 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, // _params.z == 2.0 - 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // ). {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x5f, 0x39, 0x36, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x36, 0x34, 0x2e, 0x78, 0x78, 0x78, 0x78, // _965 = _964.xxxx - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // ;. }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, // else + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x34, 0x38, 0x20, 0x3d, 0x20, 0x73, 0x5f, // float4 _948 = s_ + 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, // tex.sample(s_tex + 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x28, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, // Sampler, ((_mtl_ + 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x20, 0x2a, 0x20, 0x5f, // u.u_paintMat * _ + 0x38, 0x37, 0x31, 0x29, 0x2e, 0x78, 0x79, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, // 871).xy / _mtl_u + 0x2e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x2e, // .u_extentRadius. + 0x78, 0x79, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // xy));. + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x37, 0x3b, 0x0a, // float4 _1087;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( + 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // _mtl_u.u_params. + 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // z == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x36, // float _6 + 0x37, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x34, 0x38, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, // 78 = _948.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, // _10 + 0x38, 0x37, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x39, 0x34, 0x38, // 87 = float4(_948 + 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x36, 0x37, 0x38, 0x2c, 0x20, 0x5f, 0x36, 0x37, // .xyz * _678, _67 + 0x38, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 8);. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, // }. el + 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // se. { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x5f, 0x31, 0x30, 0x38, 0x37, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x34, 0x38, 0x3b, 0x0a, 0x20, // _1087 = _948;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, // float4 + 0x5f, 0x31, 0x30, 0x38, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _1088;. + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // if (_mtl_u.u_ + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, // params.z == 2.0) 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // . {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // _ - 0x39, 0x36, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x36, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 965 = _964;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x37, 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x39, // _967 = (_9 - 0x36, 0x35, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x69, 0x6e, // 65 * _mtl_u.u_in - 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x5f, 0x38, 0x30, 0x38, 0x20, // nerCol) * (_808 - 0x2a, 0x20, 0x5f, 0x37, 0x39, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // * _792);. - 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, // }. else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, // float4 _96 - 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, // 8;. i - 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // f (_mtl_u.u_para - 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, // ms.w == 2.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x38, 0x20, // _968 - 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, // = float4(1.0);. + 0x31, 0x30, 0x38, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x37, 0x2e, 0x78, 0x78, 0x78, // 1088 = _1087.xxx + 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // x;. } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, // . els + 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // e. {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x5f, 0x31, 0x30, 0x38, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x37, 0x3b, 0x0a, 0x20, // _1088 = _1087;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // floa - 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // t4 _969;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, // if (_mt - 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, // l_u.u_params.w = - 0x3d, 0x20, 0x33, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = 3.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // flo - 0x61, 0x74, 0x34, 0x20, 0x5f, 0x38, 0x35, 0x36, 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, // at4 _856 = s_tex - 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, // .sample(s_texSam - 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // pler, in.v_texco - 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ord0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x38, 0x20, 0x3d, // _1058 = + 0x20, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x38, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, // (_1088 * _mtl_u + 0x2e, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x29, 0x20, 0x2a, 0x20, 0x28, // .u_innerCol) * ( + 0x5f, 0x39, 0x30, 0x39, 0x20, 0x2a, 0x20, 0x5f, 0x38, 0x39, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, // _909 * _893);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // else. {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // float - 0x34, 0x20, 0x5f, 0x39, 0x36, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _962;. + 0x34, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 4 _1059;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, // if (_mtl_u. + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, // u_params.w == 2. + 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // 0). { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x5f, 0x31, 0x30, 0x35, 0x39, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, // _1059 = float4( + 0x31, 0x2e, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1.0);. + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x30, 0x3b, // float4 _1060; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, // if (_mtl_u.u_pa + 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x33, 0x2e, 0x30, 0x29, 0x0a, 0x20, // rams.w == 3.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x35, 0x37, // float4 _957 + 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, // = s_tex.sample( + 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, // s_texSampler, in + 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, // .v_texcoord0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x35, 0x3b, // float4 _1085; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, // if (_mtl_u. + 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, // u_params.z == 1. + 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x37, 0x32, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x39, // float _724 = _9 + 0x35, 0x37, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 57.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, // _1 + 0x30, 0x38, 0x35, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x39, 0x35, // 085 = float4(_95 + 0x37, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x32, 0x34, 0x2c, 0x20, 0x5f, 0x37, // 7.xyz * _724, _7 + 0x32, 0x34, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 24);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, // e + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x35, 0x37, 0x3b, 0x0a, // _1085 = _957;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, // float4 + 0x20, 0x5f, 0x31, 0x30, 0x38, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _1086;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, // if ( 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, // _mtl_u.u_params. - 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // z == 1.0). + 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // z == 2.0). 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x36, // float _6 - 0x36, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x35, 0x36, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, // 66 = _856.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x36, 0x20, 0x3d, 0x20, // _1086 = + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, // float4(fast::cla + 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x35, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x5f, // mp(((_1085.x - _ + 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, // mtl_u.u_sdf.x) / + 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, // _mtl_u.u_sdf.z) + 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, // + 0.5, 0.0, 1.0 + 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, // ) * (1.0 - fast: + 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x35, 0x2e, 0x78, // :clamp(((_1085.x + 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, // - _mtl_u.u_sdf. + 0x79, 0x29, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, // y) / _mtl_u.u_sd + 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, // f.z) + 0.5, 0.0, + 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1.0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x32, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, // _962 = floa - 0x74, 0x34, 0x28, 0x5f, 0x38, 0x35, 0x36, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x36, // t4(_856.xyz * _6 - 0x36, 0x36, 0x2c, 0x20, 0x5f, 0x36, 0x36, 0x36, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 66, _666);. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. + 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x32, 0x20, 0x3d, 0x20, 0x5f, // _962 = _ - 0x38, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 856;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x31, // _1086 = _1 + 0x30, 0x38, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 085;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, // f - 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // loat4 _963;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, // if (_mtl_u.u_par - 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, // ams.z == 2.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // _ + 0x31, 0x30, 0x36, 0x30, 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x36, 0x20, 0x2a, 0x20, // 1060 = (_1086 * + 0x5f, 0x38, 0x39, 0x33, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, // _893) * _mtl_u.u + 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // _innerCol;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, // els + 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // e. 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x33, // _963 - 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, // = float4(fast:: - 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x39, 0x36, 0x32, 0x2e, 0x78, 0x20, 0x2d, // clamp(((_962.x - - 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, // _mtl_u.u_sdf.x) - 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, // / _mtl_u.u_sdf. - 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // z) + 0.5, 0.0, 1 - 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x66, 0x61, 0x73, // .0) * (1.0 - fas - 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x39, 0x36, 0x32, 0x2e, // t::clamp(((_962. - 0x78, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, // x - _mtl_u.u_sdf - 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, // .y) / _mtl_u.u_s - 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, // df.z) + 0.5, 0.0 - 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // , 1.0)));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, // float4 _ + 0x31, 0x30, 0x36, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 1061;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, // if (_m + 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, // tl_u.u_params.w + 0x3d, 0x3d, 0x20, 0x34, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // == 4.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x39, // float4 _99 + 0x32, 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, // 2 = s_tex.sample + 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, // (s_texSampler, i + 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, // n.v_texcoord0);. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x39, // _963 = _9 - 0x36, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 62;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, // _9 - 0x36, 0x39, 0x20, 0x3d, 0x20, 0x28, 0x5f, 0x39, 0x36, 0x33, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x39, // 69 = (_963 * _79 - 0x32, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x69, 0x6e, // 2) * _mtl_u.u_in - 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // nerCol;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, // float4 _ + 0x31, 0x30, 0x30, 0x31, 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2e, 0x73, 0x61, // 1001 = s_tex2.sa + 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, // mple(s_tex2Sampl + 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, // er, in.v_texcoor + 0x64, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // d1);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // flo + 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // at4 _1083;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // if (_mtl_u.u_ + 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, // params.z == 1.0) 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x37, 0x30, // float4 _970 - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, // if (_mtl_u - 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x34, // .u_params.w == 4 - 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x38, 0x39, 0x31, 0x20, 0x3d, 0x20, // float4 _891 = - 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x74, // s_tex.sample(s_t - 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, 0x6e, 0x2e, 0x76, 0x5f, // exSampler, in.v_ - 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // texcoord0);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x37, 0x37, // float _77 + 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x39, 0x32, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 6 = _992.w;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x30, 0x30, 0x20, // float4 _900 - 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, // = s_tex2.sample( - 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x69, // s_tex2Sampler, i - 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x29, 0x3b, 0x0a, // n.v_texcoord1);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x33, 0x20, 0x3d, 0x20, // _1083 = + 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x5f, 0x39, 0x39, 0x32, 0x2e, 0x78, 0x79, 0x7a, 0x20, // float4(_992.xyz + 0x2a, 0x20, 0x5f, 0x37, 0x37, 0x36, 0x2c, 0x20, 0x5f, 0x37, 0x37, 0x36, 0x29, 0x3b, 0x0a, 0x20, // * _776, _776);. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, // float4 _ - 0x39, 0x36, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 960;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, // if - 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, // (_mtl_u.u_params - 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // .z == 1.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x37, 0x31, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x38, // float _718 = _8 - 0x39, 0x31, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 91.w;. + 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x5f, 0x39, 0x36, 0x30, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, // _960 = float4( - 0x5f, 0x38, 0x39, 0x31, 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x31, 0x38, 0x2c, // _891.xyz * _718, - 0x20, 0x5f, 0x37, 0x31, 0x38, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _718);. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, // _108 + 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x39, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 3 = _992;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, // else. + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // floa + 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // t4 _1084;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, // if (_mtl_u.u_p + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, // arams.z == 2.0). 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x39, 0x31, // _960 = _891 - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x39, 0x36, 0x31, 0x3b, // float4 _961; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x6d, 0x74, // if (_mt - 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x7a, 0x20, 0x3d, // l_u.u_params.z = - 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = 2.0). - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, // { + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x66, 0x6c, // _1084 = fl + 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, // oat4(fast::clamp + 0x28, 0x28, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x33, 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, // (((_1083.x - _mt + 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, 0x29, 0x20, 0x2f, 0x20, 0x5f, // l_u.u_sdf.x) / _ + 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, // mtl_u.u_sdf.z) + + 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x20, // 0.5, 0.0, 1.0) + 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x66, 0x61, 0x73, 0x74, 0x3a, 0x3a, 0x63, // * (1.0 - fast::c + 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x31, 0x30, 0x38, 0x33, 0x2e, 0x78, 0x20, 0x2d, // lamp(((_1083.x - + 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x79, 0x29, // _mtl_u.u_sdf.y) + 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, // / _mtl_u.u_sdf. + 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x31, // z) + 0.5, 0.0, 1 + 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .0)));. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, // _96 - 0x31, 0x20, 0x3d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x66, 0x61, 0x73, 0x74, 0x3a, // 1 = float4(fast: - 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x39, 0x36, 0x30, 0x2e, 0x78, 0x20, // :clamp(((_960.x - 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x2e, 0x78, // - _mtl_u.u_sdf.x - 0x29, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, 0x66, // ) / _mtl_u.u_sdf - 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, 0x30, 0x2c, 0x20, // .z) + 0.5, 0.0, - 0x31, 0x2e, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x66, 0x61, // 1.0) * (1.0 - fa - 0x73, 0x74, 0x3a, 0x3a, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x28, 0x28, 0x28, 0x5f, 0x39, 0x36, 0x30, // st::clamp(((_960 - 0x2e, 0x78, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x73, 0x64, // .x - _mtl_u.u_sd - 0x66, 0x2e, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // f.y) / _mtl_u.u_ - 0x73, 0x64, 0x66, 0x2e, 0x7a, 0x29, 0x20, 0x2b, 0x20, 0x30, 0x2e, 0x35, 0x2c, 0x20, 0x30, 0x2e, // sdf.z) + 0.5, 0. - 0x30, 0x2c, 0x20, 0x31, 0x2e, 0x30, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 0, 1.0)));. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, // els - 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // e. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // else. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x31, 0x20, 0x3d, 0x20, 0x5f, // _961 = _ - 0x39, 0x36, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 960;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x37, 0x30, 0x20, 0x3d, 0x20, 0x28, 0x28, // _970 = (( - 0x5f, 0x39, 0x36, 0x31, 0x20, 0x2a, 0x20, 0x5f, 0x37, 0x39, 0x32, 0x29, 0x20, 0x2a, 0x20, 0x5f, // _961 * _792) * _ - 0x39, 0x30, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, // 900) * _mtl_u.u_ - 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // innerCol;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // }. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x38, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x38, // _1084 = _108 + 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 3;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x31, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x5f, // _1061 = ((_ + 0x31, 0x30, 0x38, 0x34, 0x20, 0x2a, 0x20, 0x5f, 0x38, 0x39, 0x33, 0x29, 0x20, 0x2a, 0x20, 0x5f, // 1084 * _893) * _ + 0x31, 0x30, 0x30, 0x31, 0x29, 0x20, 0x2a, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, // 1001) * _mtl_u.u + 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // _innerCol;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, // } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // . + 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, // {. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x37, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x39, // _970 = _9 - 0x37, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 71;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, // _9 - 0x36, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x37, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 69 = _970;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x31, 0x20, 0x3d, 0x20, // _1061 = + 0x5f, 0x31, 0x30, 0x36, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // _1062;. 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, // _96 - 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x36, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 8 = _969;. - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. - 0x20, 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x37, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x36, 0x38, 0x3b, // _967 = _968; - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // . }. - 0x20, 0x20, 0x20, 0x5f, 0x39, 0x36, 0x36, 0x20, 0x3d, 0x20, 0x5f, 0x39, 0x36, 0x37, 0x3b, 0x0a, // _966 = _967;. - 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x62, 0x67, // }. out.bg - 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x3d, 0x20, 0x5f, // fx_FragData0 = _ - 0x39, 0x36, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, // 966;. return - 0x6f, 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x01, // out;.}...... + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x20, 0x5f, 0x31, 0x30, 0x36, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x31, 0x3b, 0x0a, // _1060 = _1061;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // }. + 0x20, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x30, 0x3b, // _1059 = _1060; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, // . }. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x38, // _1058 + 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // = _1059;. + 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x35, // }. _105 + 0x37, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, // 7 = _1058;. } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x31, 0x30, 0x36, // . float4 _106 + 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x28, 0x5f, 0x6d, 0x74, 0x6c, // 3;. if ((_mtl + 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, // _u.u_clipBounds. + 0x7a, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x20, 0x26, 0x26, 0x20, 0x28, 0x5f, 0x6d, 0x74, // z > 0.0) && (_mt + 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x2e, 0x77, 0x20, 0x21, // l_u.u_params.w ! + 0x3d, 0x20, 0x32, 0x2e, 0x30, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, // = 2.0)). {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x5f, 0x38, 0x33, // float2 _83 + 0x33, 0x20, 0x3d, 0x20, 0x28, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, // 3 = (in.v_positi + 0x6f, 0x6e, 0x20, 0x2d, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x63, 0x6c, // on - _mtl_u.u_cl + 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x2e, 0x78, 0x79, 0x29, 0x20, 0x2f, 0x20, 0x5f, // ipBounds.xy) / _ + 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, // mtl_u.u_clipBoun + 0x64, 0x73, 0x2e, 0x7a, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, // ds.zw;. f + 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x38, 0x33, 0x35, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x33, 0x33, // loat _835 = _833 + 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // .x;. floa + 0x74, 0x20, 0x5f, 0x38, 0x33, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x38, 0x33, 0x33, 0x2e, 0x79, 0x3b, // t _838 = _833.y; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x33, 0x20, 0x3d, // . _1063 = + 0x20, 0x5f, 0x31, 0x30, 0x35, 0x37, 0x20, 0x2a, 0x20, 0x28, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, // _1057 * (s_clip + 0x2e, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x53, 0x61, // .sample(s_clipSa + 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x2c, 0x20, 0x5f, 0x38, 0x33, 0x33, 0x29, 0x2e, 0x77, 0x20, 0x2a, // mpler, _833).w * + 0x20, 0x28, 0x28, 0x28, 0x73, 0x74, 0x65, 0x70, 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x38, // (((step(0.0, _8 + 0x33, 0x35, 0x29, 0x20, 0x2a, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, 0x30, 0x2e, 0x30, 0x2c, 0x20, // 35) * step(0.0, + 0x5f, 0x38, 0x33, 0x38, 0x29, 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, // _838)) * (1.0 - + 0x73, 0x74, 0x65, 0x70, 0x28, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x38, 0x33, 0x35, 0x29, 0x29, // step(1.0, _835)) + 0x29, 0x20, 0x2a, 0x20, 0x28, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x73, 0x74, 0x65, 0x70, 0x28, // ) * (1.0 - step( + 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x5f, 0x38, 0x33, 0x38, 0x29, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, // 1.0, _838))));. + 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, // }. else. + 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x30, 0x36, // {. _106 + 0x33, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x35, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, // 3 = _1057;. } + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, // . out.bgfx_Fr + 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x30, 0x36, 0x33, 0x3b, // agData0 = _1063; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x3b, // . return out; + 0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x30, 0x01, // .}....0. }; diff --git a/Polyfills/Canvas/Source/Shaders/spirv/fs_nanovg_fill.h b/Polyfills/Canvas/Source/Shaders/spirv/fs_nanovg_fill.h index 5cf476e0a..88a655432 100644 --- a/Polyfills/Canvas/Source/Shaders/spirv/fs_nanovg_fill.h +++ b/Polyfills/Canvas/Source/Shaders/spirv/fs_nanovg_fill.h @@ -1,26 +1,29 @@ -static const uint8_t fs_nanovg_fill_spv[6989] = +static const uint8_t fs_nanovg_fill_spv[7985] = { - 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x75, // FSH............u - 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x13, 0x01, 0x00, 0x00, 0x03, // _scissorMat..... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, // ......u_scissorE - 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x01, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, // xtScale......... - 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x01, 0xa0, 0x00, 0x01, 0x00, // ..u_params...... - 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x12, 0x01, 0xb0, 0x00, 0x01, 0x00, // .....u_sdf...... - 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, // .....u_paintMat. - 0x01, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, // .0........u_exte - 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x01, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, // ntRadius........ - 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x60, // ...u_innerCol..` - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, // ........u_outerC - 0x6f, 0x6c, 0x12, 0x01, 0x70, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, // ol..p........s_t - 0x65, 0x78, 0x30, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x26, 0x00, 0x06, 0x73, 0x5f, 0x74, // ex0.......&..s_t - 0x65, 0x78, 0x32, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x26, 0x00, 0x60, 0x1a, 0x00, // ex20.......&.`.. - 0x00, 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00, 0xcc, 0x03, 0x00, // ...#............ + 0x46, 0x53, 0x48, 0x0c, 0x1e, 0x98, 0xde, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, // .......u_scissor + 0x4d, 0x61, 0x74, 0x13, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x75, 0x5f, // Mat...........u_ + 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, // scissorExtScale. + 0x01, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, // ..........u_para + 0x6d, 0x73, 0x12, 0x01, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75, 0x5f, 0x73, // ms...........u_s + 0x64, 0x66, 0x12, 0x01, 0xb0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x70, // df...........u_p + 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x13, 0x01, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, // aintMat..0...... + 0x00, 0x0e, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, 0x69, 0x75, 0x73, // ..u_extentRadius + 0x12, 0x01, 0x90, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x5f, 0x69, 0x6e, 0x6e, // ...........u_inn + 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x60, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, // erCol..`........ + 0x75, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x12, 0x01, 0x70, 0x00, 0x01, 0x00, // u_outerCol..p... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, // .....u_clipBound + 0x73, 0x12, 0x01, 0xc0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // s...........s_te + 0x78, 0x30, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x29, 0x00, 0x06, 0x73, 0x5f, 0x74, 0x65, // x0.......)..s_te + 0x78, 0x32, 0x30, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x29, 0x00, 0x06, 0x73, 0x5f, 0x63, // x20.......)..s_c + 0x6c, 0x69, 0x70, 0x30, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x29, 0x00, 0x1c, 0x1e, 0x00, // lip0.......).... + 0x00, 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00, 0x41, 0x04, 0x00, // ...#.........A.. 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, // ................ 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, // .....GLSL.std.45 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 0............... 0x00, 0x0f, 0x00, 0x09, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, // .............mai - 0x6e, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x01, 0x00, 0x00, 0xd5, 0x01, 0x00, 0x00, 0xd8, 0x01, 0x00, // n............... - 0x00, 0xe7, 0x01, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // ................ + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x0b, 0x02, 0x00, 0x00, 0x0e, 0x02, 0x00, // n............... + 0x00, 0x1d, 0x02, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // ................ 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, // ................ 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, // .....main....... 0x00, 0x3c, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, // .<...s_texSample @@ -29,55 +32,65 @@ static const uint8_t fs_nanovg_fill_spv[6989] = 0x00, 0x43, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x32, 0x53, 0x61, 0x6d, 0x70, 0x6c, // .C...s_tex2Sampl 0x65, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x45, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x74, // er.......E...s_t 0x65, 0x78, 0x32, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, // ex2Texture...... - 0x00, 0x86, 0x00, 0x00, 0x00, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x42, 0x6c, 0x6f, 0x63, // .....UniformBloc - 0x6b, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // k............... + 0x00, 0x49, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x63, 0x6c, 0x69, 0x70, 0x53, 0x61, 0x6d, 0x70, 0x6c, // .I...s_clipSampl + 0x65, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x63, // er.......K...s_c + 0x6c, 0x69, 0x70, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, // lipTexture...... + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x55, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x42, 0x6c, 0x6f, 0x63, // .....UniformBloc + 0x6b, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // k............... 0x00, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x4d, 0x61, 0x74, 0x00, 0x00, 0x00, // .u_scissorMat... - 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x70, // .............u_p - 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x00, 0x00, // aintMat......... + 0x00, 0x06, 0x00, 0x06, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x70, // .............u_p + 0x61, 0x69, 0x6e, 0x74, 0x4d, 0x61, 0x74, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x8c, 0x00, 0x00, // aintMat......... 0x00, 0x02, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x00, // .....u_innerCol. - 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x6f, // .............u_o - 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x86, 0x00, 0x00, // uterCol......... + 0x00, 0x06, 0x00, 0x06, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x6f, // .............u_o + 0x75, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6c, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x8c, 0x00, 0x00, // uterCol......... 0x00, 0x04, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x63, 0x69, 0x73, 0x73, 0x6f, 0x72, 0x45, 0x78, // .....u_scissorEx - 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x86, 0x00, 0x00, // tScale.......... + 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, 0x00, 0x8c, 0x00, 0x00, // tScale.......... 0x00, 0x05, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x64, // .....u_extentRad - 0x69, 0x75, 0x73, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x86, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // ius............. + 0x69, 0x75, 0x73, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // ius............. 0x00, 0x75, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x05, // .u_params....... - 0x00, 0x86, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x00, 0x00, // .........u_sdf.. - 0x00, 0x05, 0x00, 0x03, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, // ................ - 0x00, 0xd2, 0x01, 0x00, 0x00, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, // .....v_position. - 0x00, 0x05, 0x00, 0x05, 0x00, 0xd5, 0x01, 0x00, 0x00, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // .........v_texco - 0x6f, 0x72, 0x64, 0x30, 0x00, 0x05, 0x00, 0x05, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x76, 0x5f, 0x74, // ord0.........v_t - 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x31, 0x00, 0x05, 0x00, 0x06, 0x00, 0xe7, 0x01, 0x00, // excoord1........ - 0x00, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x00, // .bgfx_FragData0. - 0x00, 0x47, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, // .G...<...!...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...<..."...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // .G...?...!...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...?..."...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x43, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, // .G...C...!...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x43, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...C..."...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x45, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // .G...E...!...... - 0x00, 0x47, 0x00, 0x04, 0x00, 0x45, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...E..."...... - 0x00, 0x47, 0x00, 0x03, 0x00, 0x86, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, // .G...........H.. - 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .............H.. - 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, // ................ - 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, // .H...........#.. - 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x86, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .....H.......... - 0x00, 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .....H.......... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, // .........H...... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .....#...0...H.. - 0x00, 0x86, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, // .........#...`.. - 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, // .H...........#.. - 0x00, 0x70, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // .p...H.......... - 0x00, 0x23, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, // .#.......H...... - 0x00, 0x05, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .....#.......H.. - 0x00, 0x86, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, // .........#...... - 0x00, 0x48, 0x00, 0x05, 0x00, 0x86, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, // .H...........#.. - 0x00, 0xb0, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x88, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, // .....G.......!.. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x88, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, // .....G.......".. - 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0xd2, 0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0xd5, 0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... - 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0xe7, 0x01, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x73, 0x64, 0x66, 0x00, 0x00, // .........u_sdf.. + 0x00, 0x06, 0x00, 0x07, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x63, // .............u_c + 0x6c, 0x69, 0x70, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, // lipBounds....... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x08, 0x02, 0x00, // ................ + 0x00, 0x76, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x05, 0x00, 0x05, // .v_position..... + 0x00, 0x0b, 0x02, 0x00, 0x00, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, // .....v_texcoord0 + 0x00, 0x05, 0x00, 0x05, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, // .........v_texco + 0x6f, 0x72, 0x64, 0x31, 0x00, 0x05, 0x00, 0x06, 0x00, 0x1d, 0x02, 0x00, 0x00, 0x62, 0x67, 0x66, // ord1.........bgf + 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x00, 0x00, 0x47, 0x00, 0x04, // x_FragData0..G.. + 0x00, 0x3c, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .<...!.......G.. + 0x00, 0x3c, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .<...".......G.. + 0x00, 0x3f, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .?...!.......G.. + 0x00, 0x3f, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .?...".......G.. + 0x00, 0x43, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .C...!.......G.. + 0x00, 0x43, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .C...".......G.. + 0x00, 0x45, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .E...!.......G.. + 0x00, 0x45, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .E...".......G.. + 0x00, 0x49, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .I...!.......G.. + 0x00, 0x49, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .I...".......G.. + 0x00, 0x4b, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, // .K...!.......G.. + 0x00, 0x4b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, // .K...".......G.. + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x04, 0x00, 0x8c, 0x00, 0x00, // .........H...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, // .........H...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .............H.. + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .........#...... + 0x00, 0x48, 0x00, 0x04, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // .H.............. + 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // .H.............. + 0x00, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .....H.......... + 0x00, 0x23, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, // .#...0...H...... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .....#...`...H.. + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, // .........#...p.. + 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, // .H...........#.. + 0x00, 0x80, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, // .....H.......... + 0x00, 0x23, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, // .#.......H...... + 0x00, 0x06, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .....#.......H.. + 0x00, 0x8c, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, // .........#...... + 0x00, 0x48, 0x00, 0x05, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, // .H...........#.. + 0x00, 0xc0, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, // .....G.......!.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, // .....G.......".. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x08, 0x02, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0b, 0x02, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x1d, 0x02, 0x00, 0x00, 0x1e, 0x00, 0x00, // .....G.......... 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, // .............!.. 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, // ................ 0x00, 0x16, 0x00, 0x03, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, // ......... ...... @@ -91,350 +104,400 @@ static const uint8_t fs_nanovg_fill_spv[6989] = 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .;...>...?...... 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .;...;...C...... 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .;...>...E...... - 0x00, 0x15, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .....H... ...... - 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .+...H...I...... - 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .+...H...M...... - 0x00, 0x1b, 0x00, 0x03, 0x00, 0x51, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, // .....Q.......... - 0x00, 0x6b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .k... .......+.. - 0x00, 0x6b, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .k...l.......+.. - 0x00, 0x6b, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .k...o.......+.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x05, // .....s.......,.. - 0x00, 0x0b, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, // .....v...s...s.. - 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // .+.............. - 0x3f, 0x17, 0x00, 0x04, 0x00, 0x81, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ?............... - 0x00, 0x18, 0x00, 0x04, 0x00, 0x85, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ - 0x00, 0x1e, 0x00, 0x0a, 0x00, 0x86, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, // ................ + 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3b, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .;...;...I...... + 0x00, 0x3b, 0x00, 0x04, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .;...>...K...... + 0x00, 0x15, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .....N... ...... + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .+...N...O...... + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .+...N...S...... + 0x00, 0x1b, 0x00, 0x03, 0x00, 0x57, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, // .....W.......... + 0x00, 0x71, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .q... .......+.. + 0x00, 0x71, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .q...r.......+.. + 0x00, 0x71, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .q...u.......+.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x05, // .....y.......,.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, // .....|...y...y.. + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, // .+.............. + 0x3f, 0x17, 0x00, 0x04, 0x00, 0x87, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ?............... + 0x00, 0x18, 0x00, 0x04, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ + 0x00, 0x1e, 0x00, 0x0b, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, // ................ 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, // ................ - 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x87, 0x00, 0x00, // ......... ...... - 0x00, 0x02, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x87, 0x00, 0x00, // .........;...... - 0x00, 0x88, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x89, 0x00, 0x00, // ......... ...... - 0x00, 0x02, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, // .........+...H.. - 0x00, 0x8f, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x90, 0x00, 0x00, // ......... ...... - 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........+...... - 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x2c, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ........?,...... - 0x00, 0x96, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .............+.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x2b, 0x00, 0x04, // ............@+.. - 0x00, 0x48, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // .H........... .. - 0x00, 0xae, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .............+.. - 0x00, 0x48, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .H...........+.. - 0x00, 0x6b, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .k...........+.. - 0x00, 0x6b, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, // .k.............. - 0x00, 0xd6, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, // .....+...H...... - 0x00, 0x05, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, // .....+...H...... - 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x48, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x00, // .....+...H...... - 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x4b, 0x01, 0x00, // .....,.......K.. - 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // ................ - 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x40, // .+.......O.....@ - 0x40, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x89, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, // @+.............. - 0x40, 0x20, 0x00, 0x04, 0x00, 0xd1, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, // @ .............. - 0x00, 0x3b, 0x00, 0x04, 0x00, 0xd1, 0x01, 0x00, 0x00, 0xd2, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, // .;.............. - 0x00, 0x3b, 0x00, 0x04, 0x00, 0xd1, 0x01, 0x00, 0x00, 0xd5, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, // .;.............. - 0x00, 0x3b, 0x00, 0x04, 0x00, 0xd1, 0x01, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, // .;.............. - 0x00, 0x20, 0x00, 0x04, 0x00, 0xe6, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, // . .............. - 0x00, 0x3b, 0x00, 0x04, 0x00, 0xe6, 0x01, 0x00, 0x00, 0xe7, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, // .;.............. - 0x00, 0x01, 0x00, 0x03, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xcb, 0x03, 0x00, 0x00, 0x36, 0x00, 0x05, // .............6.. - 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, // .........=...... - 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, // .=...<...=...... - 0x00, 0x40, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, // .@...?...=...... - 0x00, 0x44, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, // .D...C...=...... - 0x00, 0x46, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, // .F...E...=...... - 0x00, 0xd3, 0x01, 0x00, 0x00, 0xd2, 0x01, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, // .........=...... - 0x00, 0xd6, 0x01, 0x00, 0x00, 0xd5, 0x01, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, // .........=...... - 0x00, 0xd9, 0x01, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0x00, 0x03, 0x00, 0x00, 0xd3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0xd3, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ - 0x00, 0x50, 0x00, 0x06, 0x00, 0x81, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, // .P.............. - 0x00, 0x01, 0x03, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x89, 0x00, 0x00, // .........A...... - 0x00, 0x03, 0x03, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .........M...=.. - 0x00, 0x85, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x90, 0x00, 0x05, // ................ - 0x00, 0x81, 0x00, 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x04, 0x03, 0x00, // ................ - 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x05, 0x03, 0x00, // .O.............. - 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, // ................ - 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // ................ - 0x00, 0x06, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, 0x00, 0x08, 0x03, 0x00, // .....A.......... - 0x00, 0x88, 0x00, 0x00, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, // .........=...... - 0x00, 0x09, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // .........O...... - 0x00, 0x0a, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x03, 0x00, // ................ - 0x00, 0x07, 0x03, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // .........O...... - 0x00, 0x0f, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ - 0x00, 0x03, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, // ................ - 0x00, 0x0b, 0x03, 0x00, 0x00, 0x0f, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ - 0x00, 0x11, 0x03, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x03, 0x00, 0x00, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ - 0x00, 0x2b, 0x00, 0x00, 0x00, 0x13, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // .+.......s...... - 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0x03, 0x00, 0x00, 0x11, 0x03, 0x00, // .Q.............. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x17, 0x03, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x16, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, // .....+.......s.. - 0x00, 0x80, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x18, 0x03, 0x00, // ................ - 0x00, 0x14, 0x03, 0x00, 0x00, 0x17, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0x1c, 0x03, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ - 0x00, 0x07, 0x00, 0x00, 0x00, 0x1d, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xa8, 0x00, 0x00, // ................ - 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0x1d, 0x03, 0x00, // ................ - 0x00, 0x80, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f, 0x03, 0x00, // ................ - 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, // ................ - 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x1f, 0x03, 0x00, // ..... .......... - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0x21, 0x03, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......!...... - 0x00, 0xad, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .....o...=...... - 0x00, 0x22, 0x03, 0x00, 0x00, 0x21, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ."...!.......... - 0x00, 0x23, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x22, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x07, // .#... ..."...... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, // .....$.......%.. - 0x00, 0x80, 0x00, 0x00, 0x00, 0x23, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .....#...Q...... - 0x00, 0x26, 0x03, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, // .&.............. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x27, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, // .....'.......%.. - 0x00, 0x80, 0x00, 0x00, 0x00, 0x26, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .....&.......... - 0x00, 0x28, 0x03, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00, 0x27, 0x03, 0x00, 0x00, 0x41, 0x00, 0x06, // .(...$...'...A.. - 0x00, 0xae, 0x00, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, // .....).......... - 0x00, 0xd3, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2a, 0x02, 0x00, // .....=.......*.. - 0x00, 0x29, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0x2b, 0x02, 0x00, // .)...........+.. - 0x00, 0x2a, 0x02, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xf6, 0x02, 0x00, // .*...s.......... - 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x2b, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, // .........+...,.. - 0x00, 0x51, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x2c, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, // .Q.......,...A.. - 0x00, 0x89, 0x00, 0x00, 0x00, 0x31, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, // .....1.......I.. - 0x00, 0x3d, 0x00, 0x04, 0x00, 0x85, 0x00, 0x00, 0x00, 0x32, 0x02, 0x00, 0x00, 0x31, 0x02, 0x00, // .=.......2...1.. - 0x00, 0x90, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x33, 0x02, 0x00, 0x00, 0x02, 0x03, 0x00, // .........3...... - 0x00, 0x32, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x34, 0x02, 0x00, // .2...O.......4.. - 0x00, 0x33, 0x02, 0x00, 0x00, 0x33, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .3...3.......... - 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, 0x00, 0x36, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......6...... - 0x00, 0xe4, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x37, 0x02, 0x00, // .....=.......7.. - 0x00, 0x36, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x38, 0x02, 0x00, // .6...O.......8.. - 0x00, 0x37, 0x02, 0x00, 0x00, 0x37, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .7...7.......... - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0x39, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......9...... - 0x00, 0xe4, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... - 0x00, 0x3a, 0x02, 0x00, 0x00, 0x39, 0x02, 0x00, 0x00, 0x50, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // .:...9...P...... - 0x00, 0x2f, 0x03, 0x00, 0x00, 0x3a, 0x02, 0x00, 0x00, 0x3a, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, // ./...:...:...... - 0x00, 0x0b, 0x00, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x38, 0x02, 0x00, 0x00, 0x2f, 0x03, 0x00, // .....0...8.../.. - 0x00, 0x0c, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x32, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........2...... - 0x00, 0x04, 0x00, 0x00, 0x00, 0x34, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // .....4.......... - 0x00, 0x34, 0x03, 0x00, 0x00, 0x32, 0x03, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, // .4...2...0...Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x03, 0x00, 0x00, 0x34, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // .....6...4...... - 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x38, 0x03, 0x00, 0x00, 0x34, 0x03, 0x00, // .Q.......8...4.. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x39, 0x03, 0x00, // .............9.. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x36, 0x03, 0x00, 0x00, 0x38, 0x03, 0x00, // .....(...6...8.. - 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........:...... - 0x00, 0x25, 0x00, 0x00, 0x00, 0x39, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, // .%...9...s...... - 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, // .....<.......(.. - 0x00, 0x34, 0x03, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x07, 0x00, 0x00, // .4...v.......... - 0x00, 0x3d, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3c, 0x03, 0x00, // .=.......B...<.. - 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3e, 0x03, 0x00, 0x00, 0x3a, 0x03, 0x00, // .........>...:.. - 0x00, 0x3d, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, // .=...........@.. - 0x00, 0x3e, 0x03, 0x00, 0x00, 0x3a, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, // .>...:...A...... - 0x00, 0x3c, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, // .<...........l.. - 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x3c, 0x02, 0x00, // .=.......=...<.. - 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3e, 0x02, 0x00, 0x00, 0x3d, 0x02, 0x00, // .........>...=.. - 0x00, 0x95, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3f, 0x02, 0x00, // .............?.. - 0x00, 0x40, 0x03, 0x00, 0x00, 0x3e, 0x02, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .@...>.......... - 0x00, 0x42, 0x02, 0x00, 0x00, 0x3f, 0x02, 0x00, 0x00, 0x3d, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x08, // .B...?...=...... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x43, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, // .....C.......+.. - 0x00, 0x42, 0x02, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, // .B...s.......P.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0x45, 0x02, 0x00, 0x00, 0x43, 0x02, 0x00, 0x00, 0x43, 0x02, 0x00, // .....E...C...C.. - 0x00, 0x43, 0x02, 0x00, 0x00, 0x43, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, // .C...C...A...... - 0x00, 0x46, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .F...........=.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0x47, 0x02, 0x00, 0x00, 0x46, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, // .....G...F...A.. - 0x00, 0x90, 0x00, 0x00, 0x00, 0x48, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x00, // .....H.......... - 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x49, 0x02, 0x00, 0x00, 0x48, 0x02, 0x00, // .=.......I...H.. - 0x00, 0x0c, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x46, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........F...... - 0x00, 0x2e, 0x00, 0x00, 0x00, 0x47, 0x02, 0x00, 0x00, 0x49, 0x02, 0x00, 0x00, 0x45, 0x02, 0x00, // .....G...I...E.. - 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4d, 0x02, 0x00, 0x00, 0x28, 0x03, 0x00, // .........M...(.. - 0x00, 0x18, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x4f, 0x02, 0x00, // .............O.. - 0x00, 0x46, 0x03, 0x00, 0x00, 0x4d, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf6, 0x02, 0x00, // .F...M.......... - 0x00, 0xf8, 0x00, 0x02, 0x00, 0x51, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, // .....Q.......... - 0x00, 0x54, 0x02, 0x00, 0x00, 0x2a, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // .T...*.......... - 0x00, 0xf5, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x54, 0x02, 0x00, // .............T.. - 0x00, 0x55, 0x02, 0x00, 0x00, 0x86, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x55, 0x02, 0x00, // .U...........U.. - 0x00, 0x41, 0x00, 0x05, 0x00, 0x89, 0x00, 0x00, 0x00, 0x5a, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......Z...... - 0x00, 0x49, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x85, 0x00, 0x00, 0x00, 0x5b, 0x02, 0x00, // .I...=.......[.. - 0x00, 0x5a, 0x02, 0x00, 0x00, 0x90, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x5c, 0x02, 0x00, // .Z.............. - 0x00, 0x02, 0x03, 0x00, 0x00, 0x5b, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // .....[...O...... - 0x00, 0x5d, 0x02, 0x00, 0x00, 0x5c, 0x02, 0x00, 0x00, 0x5c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .].............. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, 0x00, 0x5e, 0x02, 0x00, // .....A.......^.. - 0x00, 0x88, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, // .........=...... - 0x00, 0x5f, 0x02, 0x00, 0x00, 0x5e, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // ._...^...O...... - 0x00, 0x60, 0x02, 0x00, 0x00, 0x5f, 0x02, 0x00, 0x00, 0x5f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .`..._..._...... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x02, 0x00, // .............a.. - 0x00, 0x5d, 0x02, 0x00, 0x00, 0x60, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x51, 0x00, 0x00, // .]...`...V...Q.. - 0x00, 0x4d, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, // .M...@...=...W.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0x4f, 0x03, 0x00, 0x00, 0x4d, 0x03, 0x00, 0x00, 0x61, 0x02, 0x00, // .....O...M...a.. - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0x65, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......e...... - 0x00, 0xad, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... - 0x00, 0x66, 0x02, 0x00, 0x00, 0x65, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, // .f...e.......... - 0x00, 0x67, 0x02, 0x00, 0x00, 0x66, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // .g...f.......... - 0x00, 0x74, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x67, 0x02, 0x00, // .t...........g.. - 0x00, 0x68, 0x02, 0x00, 0x00, 0x74, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x68, 0x02, 0x00, // .h...t.......h.. - 0x00, 0x4f, 0x00, 0x08, 0x00, 0x81, 0x00, 0x00, 0x00, 0x6a, 0x02, 0x00, 0x00, 0x4f, 0x03, 0x00, // .O.......j...O.. - 0x00, 0x4f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // .O.............. - 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6c, 0x02, 0x00, 0x00, 0x4f, 0x03, 0x00, // .Q.......l...O.. - 0x00, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x6d, 0x02, 0x00, // .............m.. - 0x00, 0x6a, 0x02, 0x00, 0x00, 0x6c, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .j...l...Q...... - 0x00, 0x70, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .p...m.......Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, // .....q...m...... - 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x72, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, // .Q.......r...m.. - 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x02, 0x00, // .....P.......s.. - 0x00, 0x70, 0x02, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x72, 0x02, 0x00, 0x00, 0x6c, 0x02, 0x00, // .p...q...r...l.. - 0x00, 0xf9, 0x00, 0x02, 0x00, 0x74, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x74, 0x02, 0x00, // .....t.......t.. - 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0x4f, 0x03, 0x00, // .............O.. - 0x00, 0x55, 0x02, 0x00, 0x00, 0x73, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, // .U...s...h...... - 0x00, 0xd6, 0x00, 0x00, 0x00, 0x77, 0x02, 0x00, 0x00, 0x66, 0x02, 0x00, 0x00, 0xa8, 0x00, 0x00, // .....w...f...... - 0x00, 0xf7, 0x00, 0x03, 0x00, 0x7b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // .....{.......... - 0x00, 0x77, 0x02, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x7b, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // .w...x...{...... - 0x00, 0x78, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x09, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x7a, 0x02, 0x00, // .x...O.......z.. - 0x00, 0xc4, 0x03, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x7b, 0x02, 0x00, // .............{.. - 0x00, 0xf8, 0x00, 0x02, 0x00, 0x7b, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // .....{.......... - 0x00, 0xc5, 0x03, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0x74, 0x02, 0x00, 0x00, 0x7a, 0x02, 0x00, // .........t...z.. - 0x00, 0x78, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, 0x00, 0x7c, 0x02, 0x00, // .x...A.......|.. - 0x00, 0x88, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, // .........=...... - 0x00, 0x7d, 0x02, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .}...|.......... - 0x00, 0x7f, 0x02, 0x00, 0x00, 0xc5, 0x03, 0x00, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, // .........}...... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x82, 0x02, 0x00, 0x00, 0x28, 0x03, 0x00, 0x00, 0x18, 0x03, 0x00, // .........(...... - 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x7f, 0x02, 0x00, // ................ - 0x00, 0x82, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf5, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ - 0x00, 0x86, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0x89, 0x02, 0x00, // ................ - 0x00, 0x2a, 0x02, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xf4, 0x02, 0x00, // .*.............. - 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x89, 0x02, 0x00, 0x00, 0x8a, 0x02, 0x00, // ................ - 0x00, 0x8b, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x8a, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ - 0x00, 0xf4, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x8b, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, // ................ - 0x00, 0xd6, 0x00, 0x00, 0x00, 0x8e, 0x02, 0x00, 0x00, 0x2a, 0x02, 0x00, 0x00, 0x4f, 0x01, 0x00, // .........*...O.. - 0x00, 0xf7, 0x00, 0x03, 0x00, 0xf3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // ................ - 0x00, 0x8e, 0x02, 0x00, 0x00, 0x8f, 0x02, 0x00, 0x00, 0xbb, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ - 0x00, 0x8f, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x51, 0x00, 0x00, 0x00, 0x56, 0x03, 0x00, // .....V...Q...V.. - 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .@...=...W...... - 0x00, 0x58, 0x03, 0x00, 0x00, 0x56, 0x03, 0x00, 0x00, 0xd6, 0x01, 0x00, 0x00, 0x41, 0x00, 0x06, // .X...V.......A.. - 0x00, 0xae, 0x00, 0x00, 0x00, 0x93, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, // ................ - 0x00, 0xbf, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x94, 0x02, 0x00, // .....=.......... - 0x00, 0x93, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0x95, 0x02, 0x00, // ................ - 0x00, 0x94, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xa2, 0x02, 0x00, // ................ - 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x95, 0x02, 0x00, 0x00, 0x96, 0x02, 0x00, // ................ - 0x00, 0xa2, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x96, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x08, // .............O.. - 0x00, 0x81, 0x00, 0x00, 0x00, 0x98, 0x02, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x58, 0x03, 0x00, // .........X...X.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // ............. .. + 0x00, 0x8d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, // .............;.. + 0x00, 0x8d, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // ............. .. + 0x00, 0x8f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .............+.. + 0x00, 0x4e, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // .N........... .. + 0x00, 0x96, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // .............+.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x2c, 0x00, 0x05, // ............?,.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x9b, 0x00, 0x00, // ................ + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .+.............. + 0x40, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // @+...N.......... + 0x00, 0x20, 0x00, 0x04, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // . .............. + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // .+...N.......... + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x71, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // .+...q.......... + 0x00, 0x2b, 0x00, 0x04, 0x00, 0x71, 0x00, 0x00, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // .+...q.......... + 0x00, 0x14, 0x00, 0x02, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, // .........+...N.. + 0x00, 0xea, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, // .........+...N.. + 0x00, 0xfe, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, // .........+...N.. + 0x00, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // .........,...... + 0x00, 0x51, 0x01, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, // .Q.............. + 0x00, 0x86, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x55, 0x01, 0x00, // .....+.......U.. + 0x00, 0x00, 0x00, 0x40, 0x40, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8f, 0x01, 0x00, // ...@@+.......... + 0x00, 0x00, 0x00, 0x80, 0x40, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0xd1, 0x01, 0x00, // ....@+...N...... + 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x07, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, // ..... .......... + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x02, 0x00, 0x00, 0x08, 0x02, 0x00, // .....;.......... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x02, 0x00, 0x00, 0x0b, 0x02, 0x00, // .....;.......... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x07, 0x02, 0x00, 0x00, 0x0e, 0x02, 0x00, // .....;.......... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, // ..... .......... + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x1d, 0x02, 0x00, // .....;.......... + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x26, 0x04, 0x00, // .............&.. + 0x00, 0x20, 0x00, 0x04, 0x00, 0x28, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, // . ...(.......... + 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .6.............. + 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x06, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....=...<...=.. + 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....@...?...=.. + 0x00, 0x06, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....D...C...=.. + 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....F...E...=.. + 0x00, 0x06, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....J...I...=.. + 0x00, 0x08, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .....L...K...=.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x09, 0x02, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x00, 0x00, 0x0b, 0x02, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0f, 0x02, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x65, 0x03, 0x00, 0x00, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .....e.......... + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x66, 0x03, 0x00, 0x00, 0x09, 0x02, 0x00, // .Q.......f...... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x06, 0x00, 0x87, 0x00, 0x00, 0x00, 0x67, 0x03, 0x00, // .....P.......g.. + 0x00, 0x65, 0x03, 0x00, 0x00, 0x66, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, // .e...f.......A.. + 0x00, 0x8f, 0x00, 0x00, 0x00, 0x68, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, // .....h.......S.. + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x69, 0x03, 0x00, 0x00, 0x68, 0x03, 0x00, // .=.......i...h.. + 0x00, 0x90, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, 0x00, 0x6a, 0x03, 0x00, 0x00, 0x67, 0x03, 0x00, // .........j...g.. + 0x00, 0x69, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x6b, 0x03, 0x00, // .i...O.......k.. + 0x00, 0x6a, 0x03, 0x00, 0x00, 0x6a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .j...j.......... + 0x00, 0x0c, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x6c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........l...... + 0x00, 0x04, 0x00, 0x00, 0x00, 0x6b, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, // .....k...A...... + 0x00, 0x6d, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .m...........=.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x6e, 0x03, 0x00, 0x00, 0x6d, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, // .....n...m...O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x6f, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, // .....o...n...n.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ + 0x00, 0x70, 0x03, 0x00, 0x00, 0x6c, 0x03, 0x00, 0x00, 0x6f, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, // .p...l...o...O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x74, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, 0x00, 0x6e, 0x03, 0x00, // .....t...n...n.. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ + 0x00, 0x75, 0x03, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x74, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, // .u...p...t...... + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x76, 0x03, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x75, 0x03, 0x00, // .....v.......u.. + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x78, 0x03, 0x00, 0x00, 0x76, 0x03, 0x00, // .Q.......x...v.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x79, 0x03, 0x00, // .............y.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x78, 0x03, 0x00, 0x00, 0x79, 0x00, 0x00, // .....+...x...y.. + 0x00, 0x86, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7b, 0x03, 0x00, // .....Q.......{.. + 0x00, 0x76, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, // .v.............. + 0x00, 0x7c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x7b, 0x03, 0x00, // .|.......+...{.. + 0x00, 0x79, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .y.............. + 0x00, 0x7d, 0x03, 0x00, 0x00, 0x79, 0x03, 0x00, 0x00, 0x7c, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, // .}...y...|...A.. + 0x00, 0x28, 0x04, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x0b, 0x02, 0x00, 0x00, 0x72, 0x00, 0x00, // .(...........r.. + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x81, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, // .=.............. + 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x82, 0x03, 0x00, 0x00, 0x81, 0x03, 0x00, // ................ + 0x00, 0xae, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x83, 0x03, 0x00, // ................ + 0x00, 0x82, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0x84, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x83, 0x03, 0x00, // ................ + 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x85, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, // ................ + 0x00, 0x84, 0x03, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x86, 0x03, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .........u...=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x87, 0x03, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0x88, 0x03, 0x00, 0x00, 0x85, 0x03, 0x00, 0x00, 0x87, 0x03, 0x00, // ................ + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x25, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x88, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, // .%...........A.. + 0x00, 0x28, 0x04, 0x00, 0x00, 0x8a, 0x03, 0x00, 0x00, 0x0b, 0x02, 0x00, 0x00, 0x75, 0x00, 0x00, // .(...........u.. + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8b, 0x03, 0x00, 0x00, 0x8a, 0x03, 0x00, // .=.............. + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x25, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x8b, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, // .%.............. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x8d, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0x8c, 0x03, 0x00, // ................ + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x63, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.......c...... + 0x00, 0xb3, 0x00, 0x00, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... + 0x00, 0x64, 0x02, 0x00, 0x00, 0x63, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, // .d...c.......... + 0x00, 0x65, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // .e...d...y...... + 0x00, 0x30, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x65, 0x02, 0x00, // .0...........e.. + 0x00, 0x66, 0x02, 0x00, 0x00, 0x8b, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x66, 0x02, 0x00, // .f...........f.. + 0x00, 0x41, 0x00, 0x05, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x6b, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.......k...... + 0x00, 0x4f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x6c, 0x02, 0x00, // .O...=.......l.. + 0x00, 0x6b, 0x02, 0x00, 0x00, 0x90, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, 0x00, 0x6d, 0x02, 0x00, // .k...........m.. + 0x00, 0x67, 0x03, 0x00, 0x00, 0x6c, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // .g...l...O...... + 0x00, 0x6e, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, 0x00, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .n...m...m...... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, 0x00, 0x70, 0x02, 0x00, // .....A.......p.. + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, // .........=...... + 0x00, 0x71, 0x02, 0x00, 0x00, 0x70, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, // .q...p...O...... + 0x00, 0x72, 0x02, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // .r...q...q...... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x73, 0x02, 0x00, // .....A.......s.. + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x74, 0x02, 0x00, 0x00, 0x73, 0x02, 0x00, 0x00, 0x50, 0x00, 0x05, // .....t...s...P.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x94, 0x03, 0x00, 0x00, 0x74, 0x02, 0x00, 0x00, 0x74, 0x02, 0x00, // .........t...t.. + 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x95, 0x03, 0x00, 0x00, 0x72, 0x02, 0x00, // .............r.. + 0x00, 0x94, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x97, 0x03, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6e, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, // .........n...... + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x99, 0x03, 0x00, 0x00, 0x97, 0x03, 0x00, 0x00, 0x95, 0x03, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x9b, 0x03, 0x00, 0x00, 0x99, 0x03, 0x00, // .Q.............. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x9d, 0x03, 0x00, // .....Q.......... + 0x00, 0x99, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0x9e, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x9b, 0x03, 0x00, // .........(...... + 0x00, 0x9d, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x9e, 0x03, 0x00, 0x00, 0x79, 0x00, 0x00, // .....%.......y.. + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xa1, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x28, 0x00, 0x00, 0x00, 0x99, 0x03, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x06, // .(.......|...... + 0x00, 0x07, 0x00, 0x00, 0x00, 0xa2, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, // .............B.. + 0x00, 0xa1, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xa3, 0x03, 0x00, // ................ + 0x00, 0x9f, 0x03, 0x00, 0x00, 0xa2, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0xa5, 0x03, 0x00, 0x00, 0xa3, 0x03, 0x00, 0x00, 0x74, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, // .........t...A.. + 0x00, 0xb4, 0x00, 0x00, 0x00, 0x76, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, // .....v.......... + 0x00, 0x72, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x77, 0x02, 0x00, // .r...=.......w.. + 0x00, 0x76, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, // .v...........x.. + 0x00, 0x77, 0x02, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .w.............. + 0x00, 0x79, 0x02, 0x00, 0x00, 0xa5, 0x03, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x88, 0x00, 0x05, // .y.......x...... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0x79, 0x02, 0x00, 0x00, 0x77, 0x02, 0x00, // .....|...y...w.. + 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, // .........}...... + 0x00, 0x2b, 0x00, 0x00, 0x00, 0x7c, 0x02, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, // .+...|...y...... + 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x7f, 0x02, 0x00, 0x00, 0x7d, 0x02, 0x00, // .P...........}.. + 0x00, 0x7d, 0x02, 0x00, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x7d, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, // .}...}...}...A.. + 0x00, 0x96, 0x00, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, // ................ + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x81, 0x02, 0x00, 0x00, 0x80, 0x02, 0x00, // .=.............. + 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, 0x00, 0x82, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.............. + 0x00, 0xff, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x83, 0x02, 0x00, // .....=.......... + 0x00, 0x82, 0x02, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xab, 0x03, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x81, 0x02, 0x00, 0x00, 0x83, 0x02, 0x00, // ................ + 0x00, 0x7f, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x87, 0x02, 0x00, // ................ + 0x00, 0x8d, 0x03, 0x00, 0x00, 0x7d, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .....}.......... + 0x00, 0x89, 0x02, 0x00, 0x00, 0xab, 0x03, 0x00, 0x00, 0x87, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ + 0x00, 0x30, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x8b, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, // .0.............. + 0x00, 0xdc, 0x00, 0x00, 0x00, 0x8e, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0x86, 0x00, 0x00, // .........d...... + 0x00, 0xf7, 0x00, 0x03, 0x00, 0x2f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // ...../.......... + 0x00, 0x8e, 0x02, 0x00, 0x00, 0x8f, 0x02, 0x00, 0x00, 0xc0, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ + 0x00, 0x8f, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x8f, 0x00, 0x00, 0x00, 0x94, 0x02, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x8b, 0x00, 0x00, // .....O...=...... + 0x00, 0x95, 0x02, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, 0x90, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, // ................ + 0x00, 0x96, 0x02, 0x00, 0x00, 0x67, 0x03, 0x00, 0x00, 0x95, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, // .....g.......O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x97, 0x02, 0x00, 0x00, 0x96, 0x02, 0x00, 0x00, 0x96, 0x02, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, // .........A...... + 0x00, 0x98, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x99, 0x02, 0x00, 0x00, 0x98, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x07, // .............O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x99, 0x02, 0x00, 0x00, 0x99, 0x02, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ + 0x00, 0x9b, 0x02, 0x00, 0x00, 0x97, 0x02, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, // .............V.. + 0x00, 0x57, 0x00, 0x00, 0x00, 0xb2, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, // .W.......@...=.. + 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb4, 0x03, 0x00, 0x00, 0xb2, 0x03, 0x00, // .W.............. + 0x00, 0x9b, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x9f, 0x02, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x9f, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, // ................ + 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa1, 0x02, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x86, 0x00, 0x00, // ................ + 0x00, 0xf7, 0x00, 0x03, 0x00, 0xae, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // ................ + 0x00, 0xa1, 0x02, 0x00, 0x00, 0xa2, 0x02, 0x00, 0x00, 0xae, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ + 0x00, 0xa2, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x08, 0x00, 0x87, 0x00, 0x00, 0x00, 0xa4, 0x02, 0x00, // .....O.......... + 0x00, 0xb4, 0x03, 0x00, 0x00, 0xb4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xa6, 0x02, 0x00, // .....Q.......... + 0x00, 0xb4, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, // ................ + 0x00, 0xa7, 0x02, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x00, 0xa6, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xaa, 0x02, 0x00, 0x00, 0xa7, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xab, 0x02, 0x00, 0x00, 0xa7, 0x02, 0x00, // .Q.............. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xac, 0x02, 0x00, // .....Q.......... + 0x00, 0xa7, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // .........P...... + 0x00, 0xad, 0x02, 0x00, 0x00, 0xaa, 0x02, 0x00, 0x00, 0xab, 0x02, 0x00, 0x00, 0xac, 0x02, 0x00, // ................ + 0x00, 0xa6, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xae, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ + 0x00, 0xae, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3f, 0x04, 0x00, // .............?.. + 0x00, 0xb4, 0x03, 0x00, 0x00, 0x8f, 0x02, 0x00, 0x00, 0xad, 0x02, 0x00, 0x00, 0xa2, 0x02, 0x00, // ................ + 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xb1, 0x02, 0x00, 0x00, 0xa0, 0x02, 0x00, // ................ + 0x00, 0xae, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xb5, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0xfa, 0x00, 0x04, 0x00, 0xb1, 0x02, 0x00, 0x00, 0xb2, 0x02, 0x00, 0x00, 0xb5, 0x02, 0x00, // ................ + 0x00, 0xf8, 0x00, 0x02, 0x00, 0xb2, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x09, 0x00, 0x0d, 0x00, 0x00, // .........O...... + 0x00, 0xb4, 0x02, 0x00, 0x00, 0x3f, 0x04, 0x00, 0x00, 0x3f, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // .....?...?...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ + 0x00, 0xb5, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xb5, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0x3f, 0x04, 0x00, 0x00, 0xae, 0x02, 0x00, // .....@...?...... + 0x00, 0xb4, 0x02, 0x00, 0x00, 0xb2, 0x02, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, // .........A...... + 0x00, 0xb6, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb7, 0x02, 0x00, 0x00, 0xb6, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb9, 0x02, 0x00, 0x00, 0x40, 0x04, 0x00, 0x00, 0xb7, 0x02, 0x00, // .........@...... + 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xbc, 0x02, 0x00, 0x00, 0x8d, 0x03, 0x00, // ................ + 0x00, 0x7d, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xbe, 0x02, 0x00, // .}.............. + 0x00, 0xb9, 0x02, 0x00, 0x00, 0xbc, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2f, 0x03, 0x00, // ............./.. + 0x00, 0xf8, 0x00, 0x02, 0x00, 0xc0, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, // ................ + 0x00, 0xc3, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // .....d.......... + 0x00, 0x2e, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xc3, 0x02, 0x00, // ................ + 0x00, 0xc4, 0x02, 0x00, 0x00, 0xc5, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xc4, 0x02, 0x00, // ................ + 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2e, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xc5, 0x02, 0x00, // ................ + 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xc8, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00, // .............d.. + 0x00, 0x55, 0x01, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x2d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // .U.......-...... + 0x00, 0xfa, 0x00, 0x04, 0x00, 0xc8, 0x02, 0x00, 0x00, 0xc9, 0x02, 0x00, 0x00, 0xf5, 0x02, 0x00, // ................ + 0x00, 0xf8, 0x00, 0x02, 0x00, 0xc9, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, // .........V...W.. + 0x00, 0xbb, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, // .....@...=...W.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0xbd, 0x03, 0x00, 0x00, 0xbb, 0x03, 0x00, 0x00, 0x0c, 0x02, 0x00, // ................ + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xcd, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.............. + 0x00, 0xb3, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... + 0x00, 0xce, 0x02, 0x00, 0x00, 0xcd, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, // ................ + 0x00, 0xcf, 0x02, 0x00, 0x00, 0xce, 0x02, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // ................ + 0x00, 0xdc, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xcf, 0x02, 0x00, // ................ + 0x00, 0xd0, 0x02, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xd0, 0x02, 0x00, // ................ + 0x00, 0x4f, 0x00, 0x08, 0x00, 0x87, 0x00, 0x00, 0x00, 0xd2, 0x02, 0x00, 0x00, 0xbd, 0x03, 0x00, // .O.............. + 0x00, 0xbd, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd4, 0x02, 0x00, 0x00, 0xbd, 0x03, 0x00, // .Q.............. + 0x00, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, 0x00, 0xd5, 0x02, 0x00, // ................ + 0x00, 0xd2, 0x02, 0x00, 0x00, 0xd4, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... + 0x00, 0xd8, 0x02, 0x00, 0x00, 0xd5, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xd9, 0x02, 0x00, 0x00, 0xd5, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xda, 0x02, 0x00, 0x00, 0xd5, 0x02, 0x00, // .Q.............. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xdb, 0x02, 0x00, // .....P.......... + 0x00, 0xd8, 0x02, 0x00, 0x00, 0xd9, 0x02, 0x00, 0x00, 0xda, 0x02, 0x00, 0x00, 0xd4, 0x02, 0x00, // ................ + 0x00, 0xf9, 0x00, 0x02, 0x00, 0xdc, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xdc, 0x02, 0x00, // ................ + 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x04, 0x00, 0x00, 0xbd, 0x03, 0x00, // .........=...... + 0x00, 0xc9, 0x02, 0x00, 0x00, 0xdb, 0x02, 0x00, 0x00, 0xd0, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, // ................ + 0x00, 0xdc, 0x00, 0x00, 0x00, 0xdf, 0x02, 0x00, 0x00, 0xce, 0x02, 0x00, 0x00, 0xae, 0x00, 0x00, // ................ + 0x00, 0xf7, 0x00, 0x03, 0x00, 0xed, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // ................ + 0x00, 0xdf, 0x02, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, 0xed, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ + 0x00, 0xe0, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xe1, 0x02, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .........r...=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xe2, 0x02, 0x00, 0x00, 0xe1, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0x3d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // .........=...... + 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0xc2, 0x03, 0x00, // ................ + 0x00, 0xe2, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xc5, 0x03, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xc6, 0x03, 0x00, 0x00, 0xc5, 0x03, 0x00, 0x00, 0x88, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0xc6, 0x03, 0x00, // ................ + 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xc7, 0x03, 0x00, // ................ + 0x00, 0x9b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0xca, 0x03, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0x79, 0x00, 0x00, // .....+.......y.. + 0x00, 0x86, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xe5, 0x02, 0x00, // .....A.......... + 0x00, 0x8e, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .........u...=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xe6, 0x02, 0x00, 0x00, 0xe5, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0xd1, 0x03, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0xe6, 0x02, 0x00, // ................ + 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd4, 0x03, 0x00, 0x00, 0xd1, 0x03, 0x00, // ................ + 0x00, 0xc6, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd5, 0x03, 0x00, // ................ + 0x00, 0xd4, 0x03, 0x00, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0xd7, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xd5, 0x03, 0x00, // .........+...... + 0x00, 0x79, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .y.............. + 0x00, 0xe9, 0x02, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0xd7, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0xca, 0x03, 0x00, 0x00, 0xe9, 0x02, 0x00, // ................ + 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xec, 0x02, 0x00, 0x00, 0xea, 0x02, 0x00, // .P.............. + 0x00, 0xea, 0x02, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ + 0x00, 0xed, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xed, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3e, 0x04, 0x00, 0x00, 0x3d, 0x04, 0x00, 0x00, 0xdc, 0x02, 0x00, // .....>...=...... + 0x00, 0xec, 0x02, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // ................ + 0x00, 0xf0, 0x02, 0x00, 0x00, 0x3e, 0x04, 0x00, 0x00, 0x7d, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, // .....>...}...A.. + 0x00, 0x96, 0x00, 0x00, 0x00, 0xf2, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, // ................ + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf2, 0x02, 0x00, // .=.............. + 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf4, 0x02, 0x00, 0x00, 0xf0, 0x02, 0x00, // ................ + 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2d, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, // .........-...... + 0x00, 0xf5, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xf8, 0x02, 0x00, // ................ + 0x00, 0x64, 0x02, 0x00, 0x00, 0x8f, 0x01, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x2c, 0x03, 0x00, // .d...........,.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xf8, 0x02, 0x00, 0x00, 0xf9, 0x02, 0x00, // ................ + 0x00, 0x2c, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xf9, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, // .,...........V.. + 0x00, 0x57, 0x00, 0x00, 0x00, 0xde, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, // .W.......@...=.. + 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xde, 0x03, 0x00, // .W.............. + 0x00, 0x0c, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, 0x00, 0xe7, 0x03, 0x00, // .....V...W...... + 0x00, 0x46, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .F...D...W...... + 0x00, 0xe9, 0x03, 0x00, 0x00, 0xe7, 0x03, 0x00, 0x00, 0x0f, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, // .............A.. + 0x00, 0xb4, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xb3, 0x00, 0x00, // ................ + 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, // .....=.......... + 0x00, 0x01, 0x03, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, // ................ + 0x00, 0x02, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x10, 0x03, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x03, 0x03, 0x00, 0x00, 0x04, 0x03, 0x00, // ................ + 0x00, 0x10, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x04, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x08, // .............O.. + 0x00, 0x87, 0x00, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00, // ................ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, // .........X...... - 0x00, 0x8e, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x98, 0x02, 0x00, // ................ - 0x00, 0x9a, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x9e, 0x02, 0x00, // .....Q.......... - 0x00, 0x9b, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0x9f, 0x02, 0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. - 0x00, 0x07, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x9b, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ - 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xa1, 0x02, 0x00, 0x00, 0x9e, 0x02, 0x00, // .P.............. - 0x00, 0x9f, 0x02, 0x00, 0x00, 0xa0, 0x02, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ - 0x00, 0xa2, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xa2, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0x8f, 0x02, 0x00, // .........X...... - 0x00, 0xa1, 0x02, 0x00, 0x00, 0x96, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, // ................ - 0x00, 0xa5, 0x02, 0x00, 0x00, 0x94, 0x02, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // ................ - 0x00, 0xb3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xa5, 0x02, 0x00, // ................ - 0x00, 0xa6, 0x02, 0x00, 0x00, 0xb3, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xa6, 0x02, 0x00, // ................ - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0xa7, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.............. - 0x00, 0xbe, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .....l...=...... - 0x00, 0xa8, 0x02, 0x00, 0x00, 0xa7, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0x5d, 0x03, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, // .].............. - 0x00, 0x07, 0x00, 0x00, 0x00, 0x5f, 0x03, 0x00, 0x00, 0x5d, 0x03, 0x00, 0x00, 0xa8, 0x02, 0x00, // ....._...]...... - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.......`...... - 0x00, 0xbe, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... - 0x00, 0x61, 0x03, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .a...`.......... - 0x00, 0x62, 0x03, 0x00, 0x00, 0x5f, 0x03, 0x00, 0x00, 0x61, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, // .b..._...a...... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x63, 0x03, 0x00, 0x00, 0x62, 0x03, 0x00, 0x00, 0x95, 0x00, 0x00, // .....c...b...... - 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x65, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........e...... - 0x00, 0x2b, 0x00, 0x00, 0x00, 0x63, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // .+...c...s...... - 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, 0x00, 0xab, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, // .A.............. - 0x00, 0xbe, 0x00, 0x00, 0x00, 0x6f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .....o...=...... - 0x00, 0xac, 0x02, 0x00, 0x00, 0xab, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ - 0x00, 0x6c, 0x03, 0x00, 0x00, 0x5d, 0x03, 0x00, 0x00, 0xac, 0x02, 0x00, 0x00, 0x88, 0x00, 0x05, // .l...].......... - 0x00, 0x07, 0x00, 0x00, 0x00, 0x6f, 0x03, 0x00, 0x00, 0x6c, 0x03, 0x00, 0x00, 0x61, 0x03, 0x00, // .....o...l...a.. - 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x6f, 0x03, 0x00, // .........p...o.. - 0x00, 0x95, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x72, 0x03, 0x00, // .............r.. - 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, // .....+...p...s.. - 0x00, 0x80, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xaf, 0x02, 0x00, // ................ - 0x00, 0x80, 0x00, 0x00, 0x00, 0x72, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .....r.......... - 0x00, 0xb0, 0x02, 0x00, 0x00, 0x65, 0x03, 0x00, 0x00, 0xaf, 0x02, 0x00, 0x00, 0x50, 0x00, 0x07, // .....e.......P.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb2, 0x02, 0x00, 0x00, 0xb0, 0x02, 0x00, 0x00, 0xb0, 0x02, 0x00, // ................ - 0x00, 0xb0, 0x02, 0x00, 0x00, 0xb0, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xb3, 0x02, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0xb3, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // ................ - 0x00, 0xc3, 0x03, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, 0xa2, 0x02, 0x00, 0x00, 0xb2, 0x02, 0x00, // ................ - 0x00, 0xa6, 0x02, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb6, 0x02, 0x00, // ................ - 0x00, 0xc3, 0x03, 0x00, 0x00, 0x18, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, // .........A...... - 0x00, 0xb8, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xb9, 0x02, 0x00, 0x00, 0xb8, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xba, 0x02, 0x00, 0x00, 0xb6, 0x02, 0x00, 0x00, 0xb9, 0x02, 0x00, // ................ - 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xbb, 0x02, 0x00, // ................ - 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xbe, 0x02, 0x00, 0x00, 0x2a, 0x02, 0x00, // .............*.. - 0x00, 0x89, 0x01, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xf2, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0xfa, 0x00, 0x04, 0x00, 0xbe, 0x02, 0x00, 0x00, 0xbf, 0x02, 0x00, 0x00, 0xf2, 0x02, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0xbf, 0x02, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x51, 0x00, 0x00, // .........V...Q.. - 0x00, 0x79, 0x03, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, // .y...@...=...W.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0x79, 0x03, 0x00, 0x00, 0xd6, 0x01, 0x00, // .....{...y...... - 0x00, 0x56, 0x00, 0x05, 0x00, 0x51, 0x00, 0x00, 0x00, 0x82, 0x03, 0x00, 0x00, 0x46, 0x00, 0x00, // .V...Q.......F.. - 0x00, 0x44, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x84, 0x03, 0x00, // .D...W.......... - 0x00, 0x82, 0x03, 0x00, 0x00, 0xd9, 0x01, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0xae, 0x00, 0x00, // .........A...... - 0x00, 0xc7, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xad, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, // ................ - 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc8, 0x02, 0x00, 0x00, 0xc7, 0x02, 0x00, // .=.............. - 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xc9, 0x02, 0x00, 0x00, 0xc8, 0x02, 0x00, // ................ - 0x00, 0x80, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xd6, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ - 0x00, 0xfa, 0x00, 0x04, 0x00, 0xc9, 0x02, 0x00, 0x00, 0xca, 0x02, 0x00, 0x00, 0xd6, 0x02, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0xca, 0x02, 0x00, 0x00, 0x4f, 0x00, 0x08, 0x00, 0x81, 0x00, 0x00, // .........O...... - 0x00, 0xcc, 0x02, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // .....{...{...... - 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0xce, 0x02, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x00, 0x05, // .....{.......... - 0x00, 0x81, 0x00, 0x00, 0x00, 0xcf, 0x02, 0x00, 0x00, 0xcc, 0x02, 0x00, 0x00, 0xce, 0x02, 0x00, // ................ - 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd2, 0x02, 0x00, 0x00, 0xcf, 0x02, 0x00, // .Q.............. - 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd3, 0x02, 0x00, // .....Q.......... - 0x00, 0xcf, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... - 0x00, 0xd4, 0x02, 0x00, 0x00, 0xcf, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x07, // .............P.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xd5, 0x02, 0x00, 0x00, 0xd2, 0x02, 0x00, 0x00, 0xd3, 0x02, 0x00, // ................ - 0x00, 0xd4, 0x02, 0x00, 0x00, 0xce, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xd6, 0x02, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0xd6, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // ................ - 0x00, 0xc0, 0x03, 0x00, 0x00, 0x7b, 0x03, 0x00, 0x00, 0xbf, 0x02, 0x00, 0x00, 0xd5, 0x02, 0x00, // .....{.......... - 0x00, 0xca, 0x02, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xd9, 0x02, 0x00, // ................ - 0x00, 0xc8, 0x02, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0xe7, 0x02, 0x00, // ................ - 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xd9, 0x02, 0x00, 0x00, 0xda, 0x02, 0x00, // ................ - 0x00, 0xe7, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xda, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, // .............A.. - 0x00, 0xae, 0x00, 0x00, 0x00, 0xdb, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, // ................ - 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0xdc, 0x02, 0x00, // .l...=.......... - 0x00, 0xdb, 0x02, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x89, 0x03, 0x00, // .....Q.......... - 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ - 0x00, 0x8b, 0x03, 0x00, 0x00, 0x89, 0x03, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0x41, 0x00, 0x06, // .............A.. - 0x00, 0xae, 0x00, 0x00, 0x00, 0x8c, 0x03, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, // ................ - 0x00, 0xbf, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8d, 0x03, 0x00, // .....=.......... - 0x00, 0x8c, 0x03, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8e, 0x03, 0x00, // ................ - 0x00, 0x8b, 0x03, 0x00, 0x00, 0x8d, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ - 0x00, 0x8f, 0x03, 0x00, 0x00, 0x8e, 0x03, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, // ................ - 0x00, 0x07, 0x00, 0x00, 0x00, 0x91, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, // .............+.. - 0x00, 0x8f, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, // .....s.......A.. - 0x00, 0xae, 0x00, 0x00, 0x00, 0xdf, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, // ................ - 0x00, 0x6f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0xe0, 0x02, 0x00, // .o...=.......... - 0x00, 0xdf, 0x02, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x98, 0x03, 0x00, // ................ - 0x00, 0x89, 0x03, 0x00, 0x00, 0xe0, 0x02, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ - 0x00, 0x9b, 0x03, 0x00, 0x00, 0x98, 0x03, 0x00, 0x00, 0x8d, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, // ................ - 0x00, 0x07, 0x00, 0x00, 0x00, 0x9c, 0x03, 0x00, 0x00, 0x9b, 0x03, 0x00, 0x00, 0x95, 0x00, 0x00, // ................ - 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x9e, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ - 0x00, 0x2b, 0x00, 0x00, 0x00, 0x9c, 0x03, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, // .+.......s...... - 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xe3, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, // ................ - 0x00, 0x9e, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xe4, 0x02, 0x00, // ................ - 0x00, 0x91, 0x03, 0x00, 0x00, 0xe3, 0x02, 0x00, 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // .........P...... - 0x00, 0xe6, 0x02, 0x00, 0x00, 0xe4, 0x02, 0x00, 0x00, 0xe4, 0x02, 0x00, 0x00, 0xe4, 0x02, 0x00, // ................ - 0x00, 0xe4, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xe7, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ - 0x00, 0xe7, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc1, 0x03, 0x00, // ................ - 0x00, 0xc0, 0x03, 0x00, 0x00, 0xd6, 0x02, 0x00, 0x00, 0xe6, 0x02, 0x00, 0x00, 0xda, 0x02, 0x00, // ................ - 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xea, 0x02, 0x00, 0x00, 0xc1, 0x03, 0x00, // ................ - 0x00, 0x18, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xed, 0x02, 0x00, // ................ - 0x00, 0xea, 0x02, 0x00, 0x00, 0x84, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x90, 0x00, 0x00, // .........A...... - 0x00, 0xef, 0x02, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .............=.. - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf0, 0x02, 0x00, 0x00, 0xef, 0x02, 0x00, 0x00, 0x85, 0x00, 0x05, // ................ - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf1, 0x02, 0x00, 0x00, 0xed, 0x02, 0x00, 0x00, 0xf0, 0x02, 0x00, // ................ - 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf2, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xf2, 0x02, 0x00, // ................ - 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xca, 0x03, 0x00, 0x00, 0xcb, 0x03, 0x00, // ................ - 0x00, 0xbb, 0x02, 0x00, 0x00, 0xf1, 0x02, 0x00, 0x00, 0xe7, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ - 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ - 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc9, 0x03, 0x00, 0x00, 0xba, 0x02, 0x00, 0x00, 0xb3, 0x02, 0x00, // ................ - 0x00, 0xca, 0x03, 0x00, 0x00, 0xf2, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf4, 0x02, 0x00, // ................ - 0x00, 0xf8, 0x00, 0x02, 0x00, 0xf4, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // ................ - 0x00, 0xc8, 0x03, 0x00, 0x00, 0x4b, 0x01, 0x00, 0x00, 0x8a, 0x02, 0x00, 0x00, 0xc9, 0x03, 0x00, // .....K.......... - 0x00, 0xf3, 0x02, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf5, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ - 0x00, 0xf5, 0x02, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc7, 0x03, 0x00, // ................ - 0x00, 0x84, 0x02, 0x00, 0x00, 0x7b, 0x02, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xf4, 0x02, 0x00, // .....{.......... - 0x00, 0xf9, 0x00, 0x02, 0x00, 0xf6, 0x02, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xf6, 0x02, 0x00, // ................ - 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xc6, 0x03, 0x00, 0x00, 0x4f, 0x02, 0x00, // .............O.. - 0x00, 0x2c, 0x02, 0x00, 0x00, 0xc7, 0x03, 0x00, 0x00, 0xf5, 0x02, 0x00, 0x00, 0x3e, 0x00, 0x03, // .,...........>.. - 0x00, 0xe7, 0x01, 0x00, 0x00, 0xc6, 0x03, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, // .............8.. - 0x00, 0x00, 0x00, 0xc0, 0x00, // ..... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ + 0x00, 0x8e, 0x00, 0x05, 0x00, 0x87, 0x00, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, // ................ + 0x00, 0x08, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, // .....Q.......... + 0x00, 0x09, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... + 0x00, 0x0d, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, // .............Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x0e, 0x03, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x50, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0f, 0x03, 0x00, 0x00, 0x0c, 0x03, 0x00, // .P.............. + 0x00, 0x0d, 0x03, 0x00, 0x00, 0x0e, 0x03, 0x00, 0x00, 0x08, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ + 0x00, 0x10, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x10, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3b, 0x04, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf9, 0x02, 0x00, // .....;.......... + 0x00, 0x0f, 0x03, 0x00, 0x00, 0x04, 0x03, 0x00, 0x00, 0xb4, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, // ................ + 0x00, 0x13, 0x03, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // ................ + 0x00, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0x13, 0x03, 0x00, // .!.............. + 0x00, 0x14, 0x03, 0x00, 0x00, 0x21, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x14, 0x03, 0x00, // .....!.......... + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x15, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.............. + 0x00, 0xc4, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .....r...=...... + 0x00, 0x16, 0x03, 0x00, 0x00, 0x15, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... + 0x00, 0xee, 0x03, 0x00, 0x00, 0x3b, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, // .....;.......... + 0x00, 0x07, 0x00, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0xee, 0x03, 0x00, 0x00, 0x16, 0x03, 0x00, // ................ + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xf1, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.............. + 0x00, 0xc4, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... + 0x00, 0xf2, 0x03, 0x00, 0x00, 0xf1, 0x03, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0xf3, 0x03, 0x00, 0x00, 0xf0, 0x03, 0x00, 0x00, 0xf2, 0x03, 0x00, 0x00, 0x81, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xf3, 0x03, 0x00, 0x00, 0x9b, 0x00, 0x00, // ................ + 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf6, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x2b, 0x00, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, // .+.......y...... + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x19, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.............. + 0x00, 0xc4, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .....u...=...... + 0x00, 0x1a, 0x03, 0x00, 0x00, 0x19, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0xfd, 0x03, 0x00, 0x00, 0xee, 0x03, 0x00, 0x00, 0x1a, 0x03, 0x00, 0x00, 0x88, 0x00, 0x05, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0xfd, 0x03, 0x00, 0x00, 0xf2, 0x03, 0x00, // ................ + 0x00, 0x81, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, // ................ + 0x00, 0x9b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x79, 0x00, 0x00, // .....+.......y.. + 0x00, 0x86, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1d, 0x03, 0x00, // ................ + 0x00, 0x86, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // ................ + 0x00, 0x1e, 0x03, 0x00, 0x00, 0xf6, 0x03, 0x00, 0x00, 0x1d, 0x03, 0x00, 0x00, 0x50, 0x00, 0x07, // .............P.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0x1e, 0x03, 0x00, // ..... .......... + 0x00, 0x1e, 0x03, 0x00, 0x00, 0x1e, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x21, 0x03, 0x00, // .............!.. + 0x00, 0xf8, 0x00, 0x02, 0x00, 0x21, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // .....!.......... + 0x00, 0x3c, 0x04, 0x00, 0x00, 0x3b, 0x04, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, // .<...;....... .. + 0x00, 0x14, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x24, 0x03, 0x00, // .............$.. + 0x00, 0x3c, 0x04, 0x00, 0x00, 0x7d, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .<...}.......... + 0x00, 0x27, 0x03, 0x00, 0x00, 0x24, 0x03, 0x00, 0x00, 0xe9, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, // .'...$.......A.. + 0x00, 0x96, 0x00, 0x00, 0x00, 0x29, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, // .....).......... + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2a, 0x03, 0x00, 0x00, 0x29, 0x03, 0x00, // .=.......*...).. + 0x00, 0x85, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x2b, 0x03, 0x00, 0x00, 0x27, 0x03, 0x00, // .........+...'.. + 0x00, 0x2a, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2c, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, // .*.......,...... + 0x00, 0x2c, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x25, 0x04, 0x00, // .,...........%.. + 0x00, 0x26, 0x04, 0x00, 0x00, 0xf5, 0x02, 0x00, 0x00, 0x2b, 0x03, 0x00, 0x00, 0x21, 0x03, 0x00, // .&.......+...!.. + 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2d, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x2d, 0x03, 0x00, // .....-.......-.. + 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x24, 0x04, 0x00, 0x00, 0xf4, 0x02, 0x00, // .........$...... + 0x00, 0xed, 0x02, 0x00, 0x00, 0x25, 0x04, 0x00, 0x00, 0x2c, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, // .....%...,...... + 0x00, 0x2e, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x2e, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x23, 0x04, 0x00, 0x00, 0x51, 0x01, 0x00, 0x00, 0xc4, 0x02, 0x00, // .....#...Q...... + 0x00, 0x24, 0x04, 0x00, 0x00, 0x2d, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x2f, 0x03, 0x00, // .$...-......./.. + 0x00, 0xf8, 0x00, 0x02, 0x00, 0x2f, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, // ...../.......... + 0x00, 0x22, 0x04, 0x00, 0x00, 0xbe, 0x02, 0x00, 0x00, 0xb5, 0x02, 0x00, 0x00, 0x23, 0x04, 0x00, // ."...........#.. + 0x00, 0x2e, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, 0x00, 0x30, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, // .........0...... + 0x00, 0x30, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x21, 0x04, 0x00, // .0...........!.. + 0x00, 0x89, 0x02, 0x00, 0x00, 0x66, 0x02, 0x00, 0x00, 0x22, 0x04, 0x00, 0x00, 0x2f, 0x03, 0x00, // .....f...".../.. + 0x00, 0x41, 0x00, 0x06, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x31, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, // .A.......1...... + 0x00, 0xd1, 0x01, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .........=...... + 0x00, 0x32, 0x03, 0x00, 0x00, 0x31, 0x03, 0x00, 0x00, 0xba, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, // .2...1.......... + 0x00, 0x33, 0x03, 0x00, 0x00, 0x32, 0x03, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x05, // .3...2...y...... + 0x00, 0xdc, 0x00, 0x00, 0x00, 0x36, 0x03, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0xae, 0x00, 0x00, // .....6...d...... + 0x00, 0xa7, 0x00, 0x05, 0x00, 0xdc, 0x00, 0x00, 0x00, 0x37, 0x03, 0x00, 0x00, 0x33, 0x03, 0x00, // .........7...3.. + 0x00, 0x36, 0x03, 0x00, 0x00, 0xf7, 0x00, 0x03, 0x00, 0x5b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // .6.......[...... + 0x00, 0xfa, 0x00, 0x04, 0x00, 0x37, 0x03, 0x00, 0x00, 0x38, 0x03, 0x00, 0x00, 0x5b, 0x03, 0x00, // .....7...8...[.. + 0x00, 0xf8, 0x00, 0x02, 0x00, 0x38, 0x03, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x96, 0x00, 0x00, // .....8...A...... + 0x00, 0x3a, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0xd1, 0x01, 0x00, 0x00, 0x3d, 0x00, 0x04, // .:...........=.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3b, 0x03, 0x00, 0x00, 0x3a, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, // .....;...:...O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3c, 0x03, 0x00, 0x00, 0x3b, 0x03, 0x00, 0x00, 0x3b, 0x03, 0x00, // .....<...;...;.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ + 0x00, 0x3d, 0x03, 0x00, 0x00, 0x09, 0x02, 0x00, 0x00, 0x3c, 0x03, 0x00, 0x00, 0x4f, 0x00, 0x07, // .=.......<...O.. + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x3b, 0x03, 0x00, 0x00, 0x3b, 0x03, 0x00, // .....@...;...;.. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, // ................ + 0x00, 0x41, 0x03, 0x00, 0x00, 0x3d, 0x03, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, // .A...=...@...Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x43, 0x03, 0x00, 0x00, 0x41, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, // .....C...A...... + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x44, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........D...... + 0x00, 0x30, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x43, 0x03, 0x00, 0x00, 0x51, 0x00, 0x05, // .0...y...C...Q.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x46, 0x03, 0x00, 0x00, 0x41, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .....F...A...... + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x47, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........G...... + 0x00, 0x30, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x46, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, // .0...y...F...... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x48, 0x03, 0x00, 0x00, 0x44, 0x03, 0x00, 0x00, 0x47, 0x03, 0x00, // .....H...D...G.. + 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4b, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, // .........K...... + 0x00, 0x30, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x43, 0x03, 0x00, 0x00, 0x83, 0x00, 0x05, // .0.......C...... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x4c, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x4b, 0x03, 0x00, // .....L.......K.. + 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4d, 0x03, 0x00, 0x00, 0x48, 0x03, 0x00, // .........M...H.. + 0x00, 0x4c, 0x03, 0x00, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x50, 0x03, 0x00, // .L...........P.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0x46, 0x03, 0x00, // .....0.......F.. + 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, 0x86, 0x00, 0x00, // .........Q...... + 0x00, 0x50, 0x03, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x03, 0x00, // .P...........R.. + 0x00, 0x4d, 0x03, 0x00, 0x00, 0x51, 0x03, 0x00, 0x00, 0x56, 0x00, 0x05, 0x00, 0x57, 0x00, 0x00, // .M...Q...V...W.. + 0x00, 0x0a, 0x04, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, // .....L...J...W.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0c, 0x04, 0x00, 0x00, 0x0a, 0x04, 0x00, 0x00, 0x41, 0x03, 0x00, // .............A.. + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x56, 0x03, 0x00, 0x00, 0x0c, 0x04, 0x00, // .Q.......V...... + 0x00, 0x03, 0x00, 0x00, 0x00, 0x85, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x58, 0x03, 0x00, // .............X.. + 0x00, 0x56, 0x03, 0x00, 0x00, 0x52, 0x03, 0x00, 0x00, 0x8e, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, // .V...R.......... + 0x00, 0x5a, 0x03, 0x00, 0x00, 0x21, 0x04, 0x00, 0x00, 0x58, 0x03, 0x00, 0x00, 0xf9, 0x00, 0x02, // .Z...!...X...... + 0x00, 0x5b, 0x03, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x5b, 0x03, 0x00, 0x00, 0xf5, 0x00, 0x07, // .[.......[...... + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x27, 0x04, 0x00, 0x00, 0x21, 0x04, 0x00, 0x00, 0x30, 0x03, 0x00, // .....'...!...0.. + 0x00, 0x5a, 0x03, 0x00, 0x00, 0x38, 0x03, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x1d, 0x02, 0x00, // .Z...8...>...... + 0x00, 0x27, 0x04, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0xd0, // .'.......8...... + 0x00, // . }; diff --git a/Polyfills/Canvas/Source/nanosvgrast.h b/Polyfills/Canvas/Source/nanosvgrast.h new file mode 100644 index 000000000..b740c316c --- /dev/null +++ b/Polyfills/Canvas/Source/nanosvgrast.h @@ -0,0 +1,1452 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NSVGrasterizer* nsvgCreateRasterizer(); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#endif // NANOSVGRAST_H + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NSVGrasterizer* nsvgCreateRasterizer() +{ + NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + free(p); + p = next; + } + + if (r->edges) free(r->edges); + if (r->points) free(r->points); + if (r->points2) free(r->points2); + if (r->scanline) free(r->scanline); + + free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, ""); + fclose(fp); +} +*/ + +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif diff --git a/Polyfills/Canvas/Source/nanovg/nanovg.cpp b/Polyfills/Canvas/Source/nanovg/nanovg.cpp index 7339fcfe2..1e295ee7c 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg.cpp +++ b/Polyfills/Canvas/Source/nanovg/nanovg.cpp @@ -22,6 +22,13 @@ #include #include "nanovg.h" +#include "../nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "../nanosvgrast.h" +#undef NANOSVGRAST_IMPLEMENTATION +#include +#include +#include #include #include "nanovg_filterstack.h" @@ -1013,6 +1020,105 @@ void nvgResetScissor(NVGcontext* ctx) state->scissor.extent[1] = -1.0f; } +void nvgClipImage(NVGcontext* ctx, int image, int x, int y, int width, int height) +{ + auto& scissor = nvg__getState(ctx)->scissor; + scissor.clipImage = image; + scissor.clipBounds[0] = float(x); + scissor.clipBounds[1] = float(y); + scissor.clipBounds[2] = float(width); + scissor.clipBounds[3] = float(height); +} + +void nvgSetViewport(NVGcontext* ctx, float width, float height) +{ + ctx->params.renderViewport(ctx->params.userPtr, width, height, ctx->devicePxRatio); + ctx->drawCallCount = 0; + ctx->fillTriCount = 0; + ctx->strokeTriCount = 0; + ctx->textTriCount = 0; +} + +void nvgRasterizeClip(NVGcontext* ctx, int width, int height, bool evenOdd, NVGclipMask& mask) +{ + // Commands already contain device-space coordinates. Rasterize their original + // winding, before NanoVG's fill tessellator reorients contours as solids. + std::vector> contours; + float minX = float(width), minY = float(height), maxX = 0, maxY = 0; + auto point = [&](float x, float y) { + if (!std::isfinite(x) || !std::isfinite(y)) + throw std::runtime_error("Canvas clip path contains non-finite coordinates"); + contours.back().insert(contours.back().end(), {x, y}); + minX = std::min(minX, x); minY = std::min(minY, y); + maxX = std::max(maxX, x); maxY = std::max(maxY, y); + }; + auto line = [&](float x, float y) { + auto& points = contours.back(); + const auto px = points[points.size() - 2], py = points.back(); + point(px, py); + point(x, y); + point(x, y); + }; + for (int i = 0; i < ctx->ncommands;) { + const float* command = ctx->commands + i; + switch (int(command[0])) { + case NVG_MOVETO: + contours.emplace_back(); + point(command[1], command[2]); + i += 3; + break; + case NVG_LINETO: + if (!contours.empty()) line(command[1], command[2]); + i += 3; + break; + case NVG_BEZIERTO: + if (!contours.empty()) { + for (int p = 1; p < 7; p += 2) point(command[p], command[p + 1]); + } + i += 7; + break; + case NVG_CLOSE: + if (!contours.empty()) line(contours.back()[0], contours.back()[1]); + ++i; + break; + case NVG_WINDING: + i += 2; + break; + default: + throw std::runtime_error("Unsupported Canvas clip path command"); + } + } + + mask.x = int(std::clamp(floorf(minX), 0.f, float(width))); + mask.y = int(std::clamp(floorf(minY), 0.f, float(height))); + mask.width = std::max(1, int(std::clamp(ceilf(maxX), 0.f, float(width))) - mask.x); + mask.height = std::max(1, int(std::clamp(ceilf(maxY), 0.f, float(height))) - mask.y); + mask.rgba.resize(size_t(mask.width) * mask.height * 4); + std::vector paths(contours.size()); + NSVGshape shape{}; + shape.fill.type = NSVG_PAINT_COLOR; + shape.fill.color = 0xffffffff; + shape.opacity = 1.f; + shape.flags = NSVG_FLAGS_VISIBLE; + shape.fillRule = evenOdd ? NSVG_FILLRULE_EVENODD : NSVG_FILLRULE_NONZERO; + for (size_t i = 0; i < contours.size(); ++i) { + if (contours[i].size() < 8) continue; + paths[i].pts = contours[i].data(); + paths[i].npts = int(contours[i].size() / 2); + paths[i].closed = 1; + paths[i].next = shape.paths; + shape.paths = &paths[i]; + } + NSVGimage image{}; + image.width = float(width); + image.height = float(height); + image.shapes = &shape; + std::unique_ptr rasterizer{nsvgCreateRasterizer(), nsvgDeleteRasterizer}; + if (!rasterizer) throw std::runtime_error("Cannot allocate Canvas clip rasterizer"); + nsvgRasterize(rasterizer.get(), &image, -float(mask.x), -float(mask.y), 1.f, + mask.rgba.data(), mask.width, mask.height, mask.width * 4); +} + // Global composite operation. void nvgGlobalCompositeOperation(NVGcontext* ctx, int op) { diff --git a/Polyfills/Canvas/Source/nanovg/nanovg.h b/Polyfills/Canvas/Source/nanovg/nanovg.h index 4478d8aa8..3a5f8252f 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg.h +++ b/Polyfills/Canvas/Source/nanovg/nanovg.h @@ -440,12 +440,21 @@ void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h); // Reset and disables scissoring. void nvgResetScissor(NVGcontext* ctx); +struct NVGclipMask { + int x = 0, y = 0, width = 1, height = 1; + std::vector rgba; +}; + struct NVGsavedPath { std::vector commands; float x, y; }; void nvgSavePath(NVGcontext* ctx, NVGsavedPath& path); void nvgRestorePath(NVGcontext* ctx, const NVGsavedPath& path); +void nvgRasterizeClip(NVGcontext* ctx, int width, int height, bool evenOdd, NVGclipMask& mask); +void nvgClipImage(NVGcontext* ctx, int image, int x, int y, int width, int height); +// Canvas flushes must not reset the drawing state or its save/restore stack. +void nvgSetViewport(NVGcontext* ctx, float width, float height); // // Paths @@ -638,6 +647,8 @@ enum NVGtexture { struct NVGscissor { float xform[6]; float extent[2]; + int clipImage; + float clipBounds[4]; }; typedef struct NVGscissor NVGscissor; diff --git a/Polyfills/Canvas/Source/nanovg/nanovg_babylon.cpp b/Polyfills/Canvas/Source/nanovg/nanovg_babylon.cpp index 6e4941740..cc4d58272 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg_babylon.cpp +++ b/Polyfills/Canvas/Source/nanovg/nanovg_babylon.cpp @@ -27,6 +27,7 @@ #include #include +#include #include "nanovg.h" #include @@ -229,6 +230,8 @@ namespace float sdfMax; float sdfBlur; float unused; + float clipBounds[4]; + int clipImage; }; struct GLNVGcontext @@ -245,14 +248,17 @@ namespace bgfx::UniformHandle u_params; bgfx::UniformHandle u_halfTexel; bgfx::UniformHandle u_sdf; + bgfx::UniformHandle u_clipBounds; bgfx::UniformHandle s_tex; bgfx::UniformHandle s_tex2; + bgfx::UniformHandle s_clip; uint64_t state; bgfx::TextureHandle th; bgfx::TextureHandle th2; + bgfx::TextureHandle thClip; bgfx::TextureHandle texMissing; bgfx::TransientVertexBuffer tvb; @@ -267,6 +273,7 @@ namespace // (a canvas painted with thousands of ops would otherwise leak thousands of // views within a single device frame -> "Too many views"). bool canvasViewNeedsRefresh; + bool deferClip; struct GLNVGtexture* textures; float view[2]; @@ -378,8 +385,10 @@ namespace gl->u_extentRadius = bgfx::createUniform("u_extentRadius", bgfx::UniformType::Vec4); gl->u_params = bgfx::createUniform("u_params", bgfx::UniformType::Vec4); gl->u_sdf = bgfx::createUniform("u_sdf", bgfx::UniformType::Vec4); + gl->u_clipBounds = bgfx::createUniform("u_clipBounds", bgfx::UniformType::Vec4); gl->s_tex = bgfx::createUniform("s_tex", bgfx::UniformType::Sampler); gl->s_tex2 = bgfx::createUniform("s_tex2", bgfx::UniformType::Sampler); + gl->s_clip = bgfx::createUniform("s_clip", bgfx::UniformType::Sampler); nanovg_filterstack::InitBgfx(); // initialize filter stack uniforms + programs gl->u_halfTexel.idx = bgfx::kInvalidHandle; @@ -556,6 +565,11 @@ namespace float invxform[6] = {}; bx::memSet(frag, 0, sizeof(*frag) ); + frag->clipImage = scissor->clipImage; + if (frag->clipImage != 0) + { + bx::memCopy(frag->clipBounds, scissor->clipBounds, sizeof(frag->clipBounds)); + } frag->innerCol = glnvg__premulColor(paint->innerColor); frag->outerCol = glnvg__premulColor(paint->outerColor); @@ -665,6 +679,17 @@ namespace gl->encoder->setUniform(gl->u_extentRadius, &frag->extent[0]); gl->encoder->setUniform(gl->u_params, &frag->feather); gl->encoder->setUniform(gl->u_sdf, &frag->sdfMin); + gl->encoder->setUniform(gl->u_clipBounds, frag->clipBounds); + if (gl->deferClip) + { + const float zero[9]{}; + const float disabledScissor[4]{1.f, 1.f, 1.f, 1.f}; + gl->encoder->setUniform(gl->u_clipBounds, zero); + gl->encoder->setUniform(gl->u_scissorMat, zero); + gl->encoder->setUniform(gl->u_scissorExtScale, disabledScissor); + } + auto* clipTexture = frag->clipImage != 0 ? glnvg__findTexture(gl, frag->clipImage) : nullptr; + gl->thClip = clipTexture != nullptr ? clipTexture->id : gl->texMissing; bgfx::TextureHandle handle = gl->texMissing; @@ -740,6 +765,46 @@ namespace gl->canvasViewNeedsRefresh = call->filterStack.HasFilters(); } + static int glnvg__paintUniformOffset(GLNVGcontext* gl, GLNVGcall* call) + { + return call->uniformOffset + (call->type == GLNVG_FILL ? gl->fragSize : 0); + } + + static bool glnvg__needsClipPass(GLNVGcontext* gl, GLNVGcall* call) + { + return call->filterStack.HasFilters() && nvg__fragUniformPtr(gl, glnvg__paintUniformOffset(gl, call))->clipImage != 0; + } + + static void glnvg__compositeClippedFilter(GLNVGcontext* gl, GLNVGcall* call, + Babylon::Graphics::FrameBuffer* input, Babylon::Graphics::FrameBuffer* output) + { + if (bgfx::getAvailTransientVertexBuffer(3, s_nvgLayout) != 3) + { + throw std::runtime_error("Cannot allocate Canvas clipped-filter vertices."); + } + bgfx::TransientVertexBuffer vertices; + bgfx::allocTransientVertexBuffer(&vertices, 3, s_nvgLayout); + const NVGvertex triangle[3]{{0.f, 0.f, 0.5f, 1.f}, {gl->view[0] * 2.f, 0.f, 0.5f, 1.f}, {0.f, gl->view[1] * 2.f, 0.5f, 1.f}}; + bx::memCopy(vertices.data, triangle, sizeof(triangle)); + gl->deferClip = false; + nvgRenderSetUniforms(gl, glnvg__paintUniformOffset(gl, call), 0, 0); + const bool bottomUp = bgfx::getCaps()->originBottomLeft; + const float paint[9]{1.f, 0.f, 0.f, 0.f, bottomUp ? -1.f : 1.f, 0.f, 0.f, bottomUp ? gl->view[1] : 0.f, 1.f}; + const float extent[4]{gl->view[0], gl->view[1], 0.f, 0.f}; + const float white[4]{1.f, 1.f, 1.f, 1.f}; + const float params[4]{1.f, 1.f, 0.f, NSVG_SHADER_FILLIMG}; + gl->encoder->setUniform(gl->u_paintMat, paint); + gl->encoder->setUniform(gl->u_extentRadius, extent); + gl->encoder->setUniform(gl->u_innerCol, white); + gl->encoder->setUniform(gl->u_params, params); + gl->encoder->setState(gl->state); + gl->encoder->setVertexBuffer(0, &vertices); + gl->encoder->setTexture(0, gl->s_tex, bgfx::getTexture(input->Handle())); + gl->encoder->setTexture(1, gl->s_tex2, gl->texMissing); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); + output->Submit(*gl->encoder, gl->prog, BGFX_DISCARD_ALL); + } + static void glnvg__fill(struct GLNVGcontext* gl, struct GLNVGcall* call) { bgfx::ProgramHandle firstProg = gl->prog; @@ -775,6 +840,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); fan(gl->encoder, paths[i].fillOffset, paths[i].fillCount); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); } @@ -801,6 +867,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb, paths[i].strokeOffset, paths[i].strokeCount); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); } } @@ -810,6 +877,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb, call->vertexOffset, call->vertexCount); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); gl->encoder->setStencil(0 | BGFX_STENCIL_TEST_NOTEQUAL | BGFX_STENCIL_FUNC_RMASK(0xff) @@ -827,6 +895,11 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; std::function finalPass = [gl, call](bgfx::ProgramHandle prog, Babylon::Graphics::FrameBuffer *inBuffer, Babylon::Graphics::FrameBuffer *outBuffer) { + if (glnvg__needsClipPass(gl, call)) + { + glnvg__compositeClippedFilter(gl, call, inBuffer, outBuffer); + return; + } gl->encoder->setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD)); @@ -836,7 +909,7 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; Babylon::Graphics::FrameBuffer *finalFrameBuffer = glnvg__beginFinalFrameBuffer(gl, call); - call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release); + call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release, glnvg__needsClipPass(gl, call)); glnvg__endFinalFrameBuffer(gl, call); } @@ -859,6 +932,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); fan(gl->encoder, paths[i].fillOffset, paths[i].fillCount); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); } @@ -874,6 +948,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb, paths[i].strokeOffset, paths[i].strokeCount); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); } } @@ -886,6 +961,11 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; std::function finalPass = [gl, call](bgfx::ProgramHandle prog, Babylon::Graphics::FrameBuffer *inBuffer, Babylon::Graphics::FrameBuffer *outBuffer) { + if (glnvg__needsClipPass(gl, call)) + { + glnvg__compositeClippedFilter(gl, call, inBuffer, outBuffer); + return; + } gl->encoder->setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD)); @@ -895,7 +975,7 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; Babylon::Graphics::FrameBuffer *finalFrameBuffer = glnvg__beginFinalFrameBuffer(gl, call); - call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release); + call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release, glnvg__needsClipPass(gl, call)); glnvg__endFinalFrameBuffer(gl, call); } @@ -917,6 +997,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb, paths[i].strokeOffset, paths[i].strokeCount); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); } }; @@ -928,6 +1009,11 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; std::function finalPass = [gl, call](bgfx::ProgramHandle prog, Babylon::Graphics::FrameBuffer *inBuffer, Babylon::Graphics::FrameBuffer *outBuffer) { + if (glnvg__needsClipPass(gl, call)) + { + glnvg__compositeClippedFilter(gl, call, inBuffer, outBuffer); + return; + } gl->encoder->setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD)); @@ -937,7 +1023,7 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; Babylon::Graphics::FrameBuffer *finalFrameBuffer = glnvg__beginFinalFrameBuffer(gl, call); - call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release); + call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release, glnvg__needsClipPass(gl, call)); glnvg__endFinalFrameBuffer(gl, call); } @@ -955,6 +1041,7 @@ namespace gl->encoder->setVertexBuffer(0, &gl->tvb, call->vertexOffset, call->vertexCount); gl->encoder->setTexture(0, gl->s_tex, gl->th); gl->encoder->setTexture(1, gl->s_tex2, gl->th2); + gl->encoder->setTexture(2, gl->s_clip, gl->thClip); outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; std::function filterPass = [gl, call](bgfx::ProgramHandle prog, Babylon::Graphics::FrameBuffer *inBuffer, Babylon::Graphics::FrameBuffer *outBuffer) { @@ -965,6 +1052,11 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; std::function finalPass = [gl, call](bgfx::ProgramHandle prog, Babylon::Graphics::FrameBuffer *inBuffer, Babylon::Graphics::FrameBuffer *outBuffer) { + if (glnvg__needsClipPass(gl, call)) + { + glnvg__compositeClippedFilter(gl, call, inBuffer, outBuffer); + return; + } gl->encoder->setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_ADD)); @@ -974,7 +1066,7 @@ namespace outBuffer->Submit(*gl->encoder, prog, BGFX_DISCARD_ALL); }; Babylon::Graphics::FrameBuffer *finalFrameBuffer = glnvg__beginFinalFrameBuffer(gl, call); - call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release); + call->filterStack.Render(firstProg, setUniform, firstPass, filterPass, finalPass, finalFrameBuffer, gl->frameBufferPool.acquire, gl->frameBufferPool.release, glnvg__needsClipPass(gl, call)); glnvg__endFinalFrameBuffer(gl, call); } } @@ -1067,6 +1159,8 @@ namespace for (uint32_t ii = 0, num = gl->ncalls; ii < num; ++ii) { struct GLNVGcall* call = &gl->calls[ii]; + // Canvas clipping is applied after filtering, not to its source. + gl->deferClip = glnvg__needsClipPass(gl, call); const GLNVGblend* blend = &call->blendFunc; gl->state = BGFX_STATE_BLEND_FUNC_SEPARATE(blend->srcRGB, blend->dstRGB, blend->srcAlpha, blend->dstAlpha) @@ -1404,6 +1498,14 @@ namespace { bgfx::destroy(gl->s_tex2); } + if (bgfx::isValid(gl->u_clipBounds)) + { + bgfx::destroy(gl->u_clipBounds); + } + if (bgfx::isValid(gl->s_clip)) + { + bgfx::destroy(gl->s_clip); + } nanovg_filterstack::DisposeBgfx(); if (bgfx::isValid(gl->u_halfTexel)) @@ -1458,10 +1560,13 @@ NVGcontext* nvgCreate(int32_t _edgeaa, bx::AllocatorI* _allocator) gl->u_params = BGFX_INVALID_HANDLE; gl->u_halfTexel = BGFX_INVALID_HANDLE; gl->u_sdf = BGFX_INVALID_HANDLE; + gl->u_clipBounds = BGFX_INVALID_HANDLE; gl->s_tex = BGFX_INVALID_HANDLE; gl->s_tex2 = BGFX_INVALID_HANDLE; + gl->s_clip = BGFX_INVALID_HANDLE; gl->th = BGFX_INVALID_HANDLE; gl->th2 = BGFX_INVALID_HANDLE; + gl->thClip = BGFX_INVALID_HANDLE; gl->texMissing = BGFX_INVALID_HANDLE; gl->textureId = static_cast(bgfx::kInvalidHandle); diff --git a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp index 948dd64f1..176e637af 100644 --- a/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp +++ b/Polyfills/Canvas/Source/nanovg/nanovg_filterstack.cpp @@ -32,7 +32,7 @@ std::regex noneRegex(R"(^\s*none\s*$)"); #define BLUR_MAX_PX 1000 #define BLUR_TAPS 13 -#define BLUR_UNIFORM_SIZE 5 // fit into vec4: ceil(BLUR_TAPS / 4) +#define BLUR_UNIFORM_SIZE 5 // Matches u_weights[5] in fs_gaussblur.sc. static const bgfx::EmbeddedShader s_embeddedShadersFilterStack[] = { @@ -240,6 +240,7 @@ void nanovg_filterstack::Render( if (sigma < 2) { std::vector kernel = CalculateGaussianKernel(sigma, BLUR_TAPS); + kernel.resize(BLUR_UNIFORM_SIZE * 4, 0.0f); setUniform(m_uniforms.u_direction, &direction, 1); setUniform(m_uniforms.u_weights, kernel.data(), BLUR_UNIFORM_SIZE); From 4733aa9236055ca7bfee775a123832455ac76e08 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:25 -0700 Subject: [PATCH 10/16] Separate mip selection from filtering and align shader varyings Keep no-mip LOD limits separate from spatial filters and pass image color space into mip generation. Match vertex/fragment varying locations, placing shared inputs before vertex-only outputs. Add native mip/filter and PNG upload regressions; defer paired Babylon.js integration tests. Defer the FXC nested-loop workaround until its SPIRV-Cross dependency lands. Review-Group: H1 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 872c2f1291162601894984f29b3d6fb062838a7a Split-Part: code --- Apps/UnitTests/CMakeLists.txt | 8 + .../JavaScript/src/tests.javaScript.all.ts | 2 +- .../JavaScript/src/tests.nativeEngine.png.ts | 221 ++++++++++------- .../Tests.NativeEngine.ImageFormats.cpp | 232 ++++++++++++++++++ .../Source/Tests.NativeEngine.Mipmaps.cpp | 89 +++++++ .../Tests.NativeEngine.TextureFormats.cpp | 60 ++++- .../Tests.ShaderCompilation.FragCoord.cpp | 71 +++++- .../Source/Tests.ShaderCompilation.cpp | 111 ++++++++- .../Babylon/Graphics/Texture.h | 3 + Core/Graphics/Source/Texture.cpp | 10 + Plugins/NativeEngine/Source/NativeEngine.cpp | 34 +-- .../Source/ShaderCompilerDXBC.cpp | 1 + .../Source/ShaderCompilerDXIL.cpp | 1 + .../Source/ShaderCompilerMetal.cpp | 1 + .../Source/ShaderCompilerTraversers.cpp | 115 +++++++++ .../Source/ShaderCompilerTraversers.h | 20 +- 16 files changed, 836 insertions(+), 143 deletions(-) create mode 100644 Apps/UnitTests/Source/Tests.NativeEngine.Mipmaps.cpp diff --git a/Apps/UnitTests/CMakeLists.txt b/Apps/UnitTests/CMakeLists.txt index e62707068..b41af4515 100644 --- a/Apps/UnitTests/CMakeLists.txt +++ b/Apps/UnitTests/CMakeLists.txt @@ -48,6 +48,7 @@ set(SOURCES "Source/Tests.JavaScript.cpp" "Source/Tests.NativeEngine.InstanceData.cpp" "Source/Tests.NativeEngine.ImageFormats.cpp" + "Source/Tests.NativeEngine.Mipmaps.cpp" "Source/Tests.NativeEngine.DepthTextures.cpp" "Source/Tests.NativeEngine.CubeRenderTargets.cpp" "Source/Tests.NativeEngine.Msaa.cpp" @@ -124,6 +125,9 @@ target_compile_definitions(UnitTests PRIVATE ${ADDITIONAL_COMPILE_DEFINITIONS}) if(BABYLON_NATIVE_PLUGIN_SHADERCOMPILER) target_link_libraries(UnitTests PRIVATE ShaderCompilerInternal) target_compile_definitions(UnitTests PRIVATE HAS_SHADER_COMPILER) + if(BABYLON_NATIVE_DISABLE_WEBMIN) + target_compile_definitions(UnitTests PRIVATE HAS_SHADER_INTERFACE_BLOCKS) + endif() endif() if(BABYLON_NATIVE_PLUGIN_TESTUTILS) @@ -157,6 +161,10 @@ if(GRAPHICS_API STREQUAL "D3D12") target_compile_definitions(UnitTests PRIVATE SKIP_RENDER_TESTS BABYLON_NATIVE_GRAPHICS_API_D3D12) endif() +if(GRAPHICS_API STREQUAL "D3D11") + target_compile_definitions(UnitTests PRIVATE BABYLON_NATIVE_GRAPHICS_API_D3D11) +endif() + if(GRAPHICS_API STREQUAL "Vulkan") target_link_libraries(UnitTests PRIVATE ShaderCompilerInternal spirv-cross-core glslang) # Vulkan packaging paths (e.g. the >16 sampler rejection) are gated on this define. diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index 2eeebc860..259ea1ba9 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -1,5 +1,6 @@ import * as Mocha from "mocha"; import { expect } from "chai"; +import { registerPngTests } from "./tests.nativeEngine.png"; import { Buffer } from "buffer"; import { RequestFile, @@ -23,7 +24,6 @@ import { BlurPostProcess } from "@babylonjs/core"; import { GradientMaterial } from "@babylonjs/materials"; -import { registerPngTests } from "./tests.nativeEngine.png"; import { registerAttributeLessInstancingTests } from "./tests.nativeEngine.attributeLessInstancing"; declare var describe: typeof Mocha.describe; diff --git a/Apps/UnitTests/JavaScript/src/tests.nativeEngine.png.ts b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.png.ts index 7fcdd04f4..bf6fa0776 100644 --- a/Apps/UnitTests/JavaScript/src/tests.nativeEngine.png.ts +++ b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.png.ts @@ -2,102 +2,145 @@ import * as Mocha from "mocha"; import { expect } from "chai"; import { Constants, NativeEngine, Scene, Texture } from "@babylonjs/core"; -const fixtures: { name: string; png: string; pixels: number[]; width: number; tolerance?: number }[] = [ - { - name: "16-bit RGBA", width: 4, tolerance: 1, - png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAYAAACprNOOAAAAI0lEQVR4nGOo/38v9Ene//8MDP//NzBAeAwMIB4D4/9/90IBNYURsvcM43UAAAAASUVORK5CYII=", - pixels: [128, 222, 227, 255, 0, 255, 128, 128, 222, 227, 0, 0, 255, 0, 255, 222], - }, - { - name: "16-bit RGB", width: 4, tolerance: 1, - png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAIAAAAmzkTZAAAAG0lEQVR4nGOo/38v9EkeA8P//w0MMBYD4/9/AKWbDQOAUd17AAAAAElFTkSuQmCC", - pixels: [128, 222, 227, 255, 0, 255, 128, 255, 222, 227, 0, 255, 255, 0, 255, 255], - }, - { - name: "16-bit grayscale", width: 4, tolerance: 1, - png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAAAAACMx4xSAAAAEUlEQVR4nGOo/38v9Ene//8AGa4GAvbhooAAAAAASUVORK5CYII=", - pixels: [128, 128, 128, 255, 222, 222, 222, 255, 227, 227, 227, 255, 255, 255, 255, 255], - }, - { - name: "16-bit grayscale with alpha", width: 4, tolerance: 1, - png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAQAAAADpRsFAAAAF0lEQVR4nGOo/////73Q+v9P8hgYQCwAYP8KsdIXi8oAAAAASUVORK5CYII=", - pixels: [128, 128, 128, 255, 222, 222, 222, 128, 227, 227, 227, 0, 255, 255, 255, 222], - }, - { - name: "8-bit opaque grayscale", width: 3, - png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAADElEQVR42mNgqP8PAAIBAX+LG2RhAAAAAElFTkSuQmCC", - pixels: [0, 0, 0, 255, 127, 127, 127, 255, 255, 255, 255, 255], - }, - { - name: "8-bit grayscale with alpha", width: 3, - png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAQAAACx6dw/AAAAD0lEQVR4nGNgYKh3+P8fAAXAAr4pW6ZDAAAAAElFTkSuQmCC", - pixels: [0, 0, 0, 0, 127, 127, 127, 64, 255, 255, 255, 255], - }, +const fixtures: { name: string; png: string; pixels: number[]; width?: number; height?: number; tolerance?: number }[] = [ + { + name: "16-bit RGBA", + tolerance: 1, + png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAYAAACprNOOAAAAI0lEQVR4nGOo/38v9Ene//8MDP//NzBAeAwMIB4D4/9/90IBNYURsvcM43UAAAAASUVORK5CYII=", + pixels: [128, 222, 227, 255, 0, 255, 128, 128, 222, 227, 0, 0, 255, 0, 255, 222], + }, + { + name: "16-bit RGB", + tolerance: 1, + png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAIAAAAmzkTZAAAAG0lEQVR4nGOo/38v9EkeA8P//w0MMBYD4/9/AKWbDQOAUd17AAAAAElFTkSuQmCC", + pixels: [128, 222, 227, 255, 0, 255, 128, 255, 222, 227, 0, 255, 255, 0, 255, 255], + }, + { + name: "16-bit grayscale", + tolerance: 1, + png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAAAAACMx4xSAAAAEUlEQVR4nGOo/38v9Ene//8AGa4GAvbhooAAAAAASUVORK5CYII=", + pixels: [128, 128, 128, 255, 222, 222, 222, 255, 227, 227, 227, 255, 255, 255, 255, 255], + }, + { + name: "16-bit grayscale with alpha", + tolerance: 1, + png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAQAAAADpRsFAAAAF0lEQVR4nGOo/////73Q+v9P8hgYQCwAYP8KsdIXi8oAAAAASUVORK5CYII=", + pixels: [128, 128, 128, 255, 222, 222, 222, 128, 227, 227, 227, 0, 255, 255, 255, 222], + }, + { + name: "8-bit RGB color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAIAAACUgoPjAAAABnRSTlMAAAAAAABupgeRAAAAD0lEQVR42mNgAAFGLhE5AAByAD7AYQaiAAAAAElFTkSuQmCC", + width: 3, + pixels: [0, 0, 0, 0, 0, 0, 1, 255, 10, 20, 30, 255], + }, + { + name: "8-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAAAnRSTlMAf7YpoZUAAAAMSURBVHjaY2Co/w8AAgEBf4sbZGEAAAAASUVORK5CYII=", + width: 3, + pixels: [0, 0, 0, 255, 127, 127, 127, 0, 255, 255, 255, 255], + }, + { + name: "2-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAUAAAACAgAAAAD/sVEgAAAAAnRSTlMAApidrBQAAAAOSURBVHjaY5B2YJjcAAADMwFvhMae/wAAAABJRU5ErkJggg==", + width: 5, + height: 2, + pixels: [ + 0, 0, 0, 255, 85, 85, 85, 255, 170, 170, 170, 0, 255, 255, 255, 255, 85, 85, 85, 255, + 170, 170, 170, 0, 85, 85, 85, 255, 0, 0, 0, 255, 255, 255, 255, 255, 170, 170, 170, 0, + ], + }, + { + name: "16-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABEAAAAABuG5crAAAAAnRSTlMSNC/TSV4AAAAPSURBVHjaYxAyETJdfRYABIECBqQr4XkAAAAASUVORK5CYII=", + width: 3, + pixels: [18, 18, 18, 0, 18, 18, 18, 255, 171, 171, 171, 255], + }, + { + name: "16-bit RGB color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABEAIAAADEEl+gAAAABnRSTlMSNFZ4mryJ5E7mAAAAHElEQVR42mMQMgmrmLVHyDSsctbe1WdVMv7dAQBEhQi2aXh0TAAAAABJRU5ErkJggg==", + width: 3, + pixels: [18, 86, 154, 0, 18, 86, 154, 255, 171, 36, 254, 255], + }, + { + name: "8-bit opaque grayscale", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAADElEQVR42mNgqP8PAAIBAX+LG2RhAAAAAElFTkSuQmCC", + width: 3, + pixels: [0, 0, 0, 255, 127, 127, 127, 255, 255, 255, 255, 255], + }, + { + name: "8-bit grayscale with alpha", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAQAAACx6dw/AAAAD0lEQVR4nGNgYKh3+P8fAAXAAr4pW6ZDAAAAAElFTkSuQmCC", + width: 3, + pixels: [0, 0, 0, 0, 127, 127, 127, 64, 255, 255, 255, 255], + }, ]; function expectPixels(pixels: Uint8Array, fixture: typeof fixtures[number]): void { - expect(pixels.length).to.equal(fixture.pixels.length); - // WIC and bundled decoders quantize 16-bit channels to 8-bit with different rounding. - for (let index = 0; index < pixels.length; ++index) { - const expected = fixture.pixels[index]; - const tolerance = expected === 0 || expected === 255 ? 0 : fixture.tolerance ?? 0; - expect(pixels[index], `channel ${index}`).to.be.closeTo(expected, tolerance); - } + expect(pixels.length).to.equal(fixture.pixels.length); + // WIC and bundled decoders quantize 16-bit channels to 8-bit with different rounding. + for (let index = 0; index < pixels.length; ++index) { + const expected = fixture.pixels[index]; + const tolerance = expected === 0 || expected === 255 ? 0 : fixture.tolerance ?? 0; + expect(pixels[index], `channel ${index}`).to.be.closeTo(expected, tolerance); + } } export function registerPngTests( - describe: typeof Mocha.describe, - it: typeof Mocha.it, - enabled: boolean + describe: typeof Mocha.describe, + it: typeof Mocha.it, + enabled: boolean ): void { - describe("NativeEngine PNG decoding", function () { - this.timeout(10000); - const test = enabled ? it : it.skip; - for (const fixture of fixtures) { - for (const generateMips of [false, true]) { - test(`uploads ${fixture.name} PNG as RGBA8 (mips ${generateMips})`, async function () { - const engine = new NativeEngine(); - const scene = new Scene(engine); - try { - const texture = await new Promise((resolve, reject) => { - const image = new Texture( - "data:image/png;base64," + fixture.png, scene, !generateMips, false, - Constants.TEXTURE_NEAREST_SAMPLINGMODE, () => resolve(image), - message => reject(new Error(message || "PNG texture load failed")) - ); - }); - const pixels = await texture.readPixels(); - if (!(pixels instanceof Uint8Array)) { - throw new Error("Expected unsigned-byte PNG texture readback"); - } - expectPixels(pixels, fixture); - expect(texture.getSize().width).to.equal(fixture.width); - expect(texture.getSize().height).to.equal(1); - if (generateMips) { - const mipWidth = Math.max(1, fixture.width >> 1); - const mip = await texture.readPixels(0, 1, null, true, false, 0, 0, mipWidth, 1); - expect(mip instanceof Uint8Array).to.equal(true); - expect(mip?.byteLength).to.equal(mipWidth * 4); + describe("NativeEngine PNG decoding", function () { + this.timeout(10000); + const test = enabled ? it : it.skip; + for (const fixture of fixtures) { + const width = fixture.width ?? 4; + const height = fixture.height ?? 1; + for (const generateMips of [false, true]) { + test(`uploads ${fixture.name} PNG as browser RGBA8 (mips ${generateMips})`, async function () { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = await new Promise((resolve, reject) => { + const image = new Texture( + "data:image/png;base64," + fixture.png, scene, !generateMips, false, + Constants.TEXTURE_NEAREST_SAMPLINGMODE, () => resolve(image), + message => reject(new Error(message || "PNG texture load failed")) + ); + }); + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected unsigned-byte PNG texture readback"); + } + expectPixels(pixels, fixture); + expect(texture.getSize().width).to.equal(width); + expect(texture.getSize().height).to.equal(height); + if (generateMips) { + const mipWidth = Math.max(1, width >> 1); + const mipHeight = Math.max(1, height >> 1); + // Read only the valid mip extent for odd-sized PNG fixtures. + const mip = await texture.readPixels(0, 1, null, true, false, 0, 0, mipWidth, mipHeight); + expect(mip instanceof Uint8Array).to.equal(true); + expect(mip?.byteLength).to.equal(mipWidth * mipHeight * 4); + } + } finally { + scene.dispose(); + engine.dispose(); + } + }); } - } finally { - scene.dispose(); - engine.dispose(); - } - }); - } - - test(`decodes ${fixture.name} PNG through Canvas images`, async function () { - const engine = new NativeEngine(); - try { - const bitmap = await engine._createImageBitmapFromSource("data:image/png;base64," + fixture.png); - expect(bitmap.width).to.equal(fixture.width); - expect(bitmap.height).to.equal(1); - const pixels = engine.resizeImageBitmap(bitmap, fixture.width, 1); - expectPixels(pixels, fixture); - } finally { - engine.dispose(); + test(`decodes ${fixture.name} PNG through Canvas images`, async function () { + const engine = new NativeEngine(); + try { + const bitmap = await engine._createImageBitmapFromSource("data:image/png;base64," + fixture.png); + expect(bitmap.width).to.equal(width); + expect(bitmap.height).to.equal(height); + const pixels = engine.resizeImageBitmap(bitmap, width, height); + expectPixels(pixels, fixture); + } finally { + engine.dispose(); + } + }); } - }); - } - }); + }); } diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.ImageFormats.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.ImageFormats.cpp index dd7fdcfc9..3d095af50 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.ImageFormats.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.ImageFormats.cpp @@ -1,13 +1,78 @@ #include +#include #include #include #include +#include +#include #include +#include +#include namespace { using Image = std::unique_ptr; + + uint8_t HexNibble(char value) + { + return static_cast(value <= '9' ? value - '0' : value - 'a' + 10); + } + + std::vector DecodeHex(std::string_view hex) + { + std::vector bytes(hex.size() / 2); + for (size_t index = 0; index < bytes.size(); ++index) + { + bytes[index] = static_cast((HexNibble(hex[index * 2]) << 4) | HexNibble(hex[index * 2 + 1])); + } + return bytes; + } + + Image ParsePng(bx::AllocatorI& allocator, std::string_view hex) + { + const auto bytes = DecodeHex(hex); + Image image{bimg::imageParse(&allocator, bytes.data(), static_cast(bytes.size())), bimg::imageFree}; + if (image) + { + // WIC already returns RGBA8; other decoders retain grayscale or 16-bit channels. + image.reset(Babylon::Graphics::NormalizePngImage(allocator, image.release())); + if (image && image->m_format != bimg::TextureFormat::RGBA8) + { + image.reset(bimg::imageConvert(&allocator, bimg::TextureFormat::RGBA8, *image, false)); + } + } + return image; + } + + void ExpectKeyedGrayscale8( + bx::AllocatorI& allocator, + std::string_view hex, + uint32_t width, + std::initializer_list expectedColor, + std::initializer_list expectedAlpha) + { + Image image{ParsePng(allocator, hex)}; + ASSERT_NE(image, nullptr); + ASSERT_EQ(image->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(image->m_hasAlpha); + ASSERT_NE(width, 0); + ASSERT_EQ(expectedColor.size(), expectedAlpha.size()); + ASSERT_EQ(image->m_width, width); + ASSERT_EQ(image->m_height, expectedColor.size() / width); + ASSERT_EQ(image->m_size, expectedColor.size() * 4); + + const auto* actual = static_cast(image->m_data); + auto color = expectedColor.begin(); + auto alpha = expectedAlpha.begin(); + for (size_t index = 0; index < expectedColor.size(); ++index, ++color, ++alpha) + { + EXPECT_EQ(actual[index * 4], *color) << index; + EXPECT_EQ(actual[index * 4 + 1], *color) << index; + EXPECT_EQ(actual[index * 4 + 2], *color) << index; + EXPECT_EQ(actual[index * 4 + 3], *alpha) << index; + } + } } TEST(NativeEngineImageFormats, Png16UsesBrowserHalfFloatQuantization) @@ -105,3 +170,170 @@ TEST(NativeEngineImageFormats, PreservesOrdinaryAndAuthoredHighPrecisionFormats) } } } + +TEST(NativeEngineImageFormats, PngTransparencyKeyExpandsPackedGrayscaleOddWidths) +{ + bx::DefaultAllocator allocator; + + ExpectKeyedGrayscale8( + allocator, + "89504e470d0a1a0a0000000d4948445200000005000000020100000000b8112bf00000000274524e5300007693cd380000000c4944415478da63886058000001ac00f90e3932ac0000000049454e44ae426082", + 5, + {0, 255, 0, 255, 255, 255, 0, 255, 0, 0}, + {0, 255, 0, 255, 255, 255, 0, 255, 0, 0}); + ExpectKeyedGrayscale8( + allocator, + "89504e470d0a1a0a0000000d4948445200000005000000020200000000ffb151200000000274524e530002989dac140000000e4944415478da6390766098dc00000333016f84c69eff0000000049454e44ae426082", + 5, + {0, 85, 170, 255, 85, 170, 85, 0, 255, 170}, + {255, 255, 0, 255, 255, 0, 255, 255, 255, 0}); + ExpectKeyedGrayscale8( + allocator, + "89504e470d0a1a0a0000000d49484452000000030000000204000000007defd4c70000000274524e530007e8f7589b0000000e4944415478da6360ffc050f8000005ab02494e4b78130000000049454e44ae426082", + 3, + {0, 119, 255, 119, 17, 238}, + {255, 0, 255, 0, 255, 255}); +} + +TEST(NativeEngineImageFormats, PngTransparencyKeyExpandsGrayscale8) +{ + bx::DefaultAllocator allocator; + ExpectKeyedGrayscale8( + allocator, + "89504e470d0a1a0a0000000d49484452000000030000000108000000003e8b4b680000000274524e53007fb629a1950000000c4944415478da6360a8ff0f000201017f8b1b64610000000049454e44ae426082", + 3, + {0, 127, 255}, + {255, 0, 255}); +} + +TEST(NativeEngineImageFormats, PngTransparencyKeyMatchesGrayscale16BeforeQuantization) +{ + bx::DefaultAllocator allocator; + Image image{ParsePng( + allocator, + "89504e470d0a1a0a0000000d49484452000000030000000110000000006e1b972b0000000274524e5312342fd3495e0000000f4944415478da63103211325d7d160004810206a42be1790000000049454e44ae426082")}; + ASSERT_NE(image, nullptr); + ASSERT_EQ(image->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(image->m_hasAlpha); + ASSERT_EQ(image->m_size, 12); + const auto* actual = static_cast(image->m_data); + const std::array expected{ + 18, 18, 18, 0, + 18, 18, 18, UINT8_MAX, + 171, 171, 171, UINT8_MAX}; + for (size_t index = 0; index < expected.size(); ++index) + { + EXPECT_EQ(actual[index], expected[index]) << index; + } + +} + +TEST(NativeEngineImageFormats, PngTransparencyKeyExpandsRgb8) +{ + bx::DefaultAllocator allocator; + Image image{ParsePng( + allocator, + "89504e470d0a1a0a0000000d4948445200000003000000010802000000948283e30000000674524e530000000000006ea607910000000f4944415478da63600001462e1139000072003ec06106a20000000049454e44ae426082")}; + ASSERT_NE(image, nullptr); + EXPECT_EQ(image->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(image->m_hasAlpha); + const std::array expected{ + 0, 0, 0, 0, + 0, 0, 1, UINT8_MAX, + 10, 20, 30, UINT8_MAX}; + ASSERT_EQ(image->m_size, expected.size()); + const auto* actual = static_cast(image->m_data); + for (size_t index = 0; index < expected.size(); ++index) + { + EXPECT_EQ(actual[index], expected[index]) << index; + } +} + +TEST(NativeEngineImageFormats, PngTransparencyKeyMatchesRgb16BeforeQuantization) +{ + bx::DefaultAllocator allocator; + Image image{ParsePng( + allocator, + "89504e470d0a1a0a0000000d4948445200000003000000011002000000c4125fa00000000674524e53123456789abc89e44ee60000001c4944415478da63103209ab98b547c834ac72d6ded5675532fedd0100448508b66978744c0000000049454e44ae426082")}; + ASSERT_NE(image, nullptr); + ASSERT_EQ(image->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(image->m_hasAlpha); + const std::array expected{ + 18, 86, 154, 0, + 18, 86, 154, UINT8_MAX, + 171, 36, 254, UINT8_MAX}; + ASSERT_EQ(image->m_size, expected.size()); + const auto* actual = static_cast(image->m_data); + for (size_t index = 0; index < expected.size(); ++index) + { + EXPECT_EQ(actual[index], expected[index]) << index; + } + +} + +TEST(NativeEngineImageFormats, PngWithoutTransparencyKeyKeepsOpaquePixels) +{ + bx::DefaultAllocator allocator; + Image grayscale{ParsePng( + allocator, + "89504e470d0a1a0a0000000d49484452000000030000000108000000003e8b4b680000000c4944415478da6360a8ff0f000201017f8b1b64610000000049454e44ae426082")}; + ASSERT_NE(grayscale, nullptr); + ASSERT_EQ(grayscale->m_format, bimg::TextureFormat::RGBA8); + EXPECT_FALSE(grayscale->m_hasAlpha); + ASSERT_EQ(grayscale->m_size, 12); + const auto* gray = static_cast(grayscale->m_data); + const std::array expectedGray{0, 0, 0, 255, 127, 127, 127, 255, 255, 255, 255, 255}; + for (size_t index = 0; index < expectedGray.size(); ++index) + { + EXPECT_EQ(gray[index], expectedGray[index]) << index; + } + + Image rgb{ParsePng( + allocator, + "89504e470d0a1a0a0000000d49484452000000020000000108020000007b40e8dd0000000f4944415478da63606060e012910300006b003d4c4545fc0000000049454e44ae426082")}; + ASSERT_NE(rgb, nullptr); + ASSERT_EQ(rgb->m_format, bimg::TextureFormat::RGBA8); + EXPECT_FALSE(rgb->m_hasAlpha); + const std::array expected{0, 0, 0, 255, 10, 20, 30, 255}; + ASSERT_EQ(rgb->m_size, expected.size()); + const auto* actual = static_cast(rgb->m_data); + for (size_t index = 0; index < expected.size(); ++index) + { + EXPECT_EQ(actual[index], expected[index]) << index; + } +} + +TEST(NativeEngineImageFormats, PngPaletteAndPerPixelAlphaPathsRemainUnchanged) +{ + bx::DefaultAllocator allocator; + Image palette{ParsePng( + allocator, + "89504e470d0a1a0a0000000d49484452000000030000000108030000002c3ee48600000009504c5445010203040506070809258556f00000000374524e530080ffecf7b3180000000c4944415478da63606064020000080004081d630a0000000049454e44ae426082")}; + ASSERT_NE(palette, nullptr); + EXPECT_EQ(palette->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(palette->m_hasAlpha); + const std::array expectedPalette{ + 1, 2, 3, 0, + 4, 5, 6, 128, + 7, 8, 9, UINT8_MAX}; + ASSERT_EQ(palette->m_size, expectedPalette.size()); + const auto* palettePixels = static_cast(palette->m_data); + for (size_t index = 0; index < expectedPalette.size(); ++index) + { + EXPECT_EQ(palettePixels[index], expectedPalette[index]) << index; + } + + Image rgba{ParsePng( + allocator, + "89504e470d0a1a0a0000000d4948445200000002000000010806000000f4227f8a000000114944415478da63606462666061656b000000dc0096e861a3670000000049454e44ae426082")}; + ASSERT_NE(rgba, nullptr); + EXPECT_EQ(rgba->m_format, bimg::TextureFormat::RGBA8); + EXPECT_TRUE(rgba->m_hasAlpha); + const std::array expectedRgba{1, 2, 3, 0, 4, 5, 6, 128}; + ASSERT_EQ(rgba->m_size, expectedRgba.size()); + const auto* rgbaPixels = static_cast(rgba->m_data); + for (size_t index = 0; index < expectedRgba.size(); ++index) + { + EXPECT_EQ(rgbaPixels[index], expectedRgba[index]) << index; + } +} diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.Mipmaps.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.Mipmaps.cpp new file mode 100644 index 000000000..45c20761e --- /dev/null +++ b/Apps/UnitTests/Source/Tests.NativeEngine.Mipmaps.cpp @@ -0,0 +1,89 @@ +#include +#include +#include + +#include +#include +#include + +namespace +{ + using Image = std::unique_ptr; + + double DecodeSRGB(double value) + { + return value <= 0.04045 ? value / 12.92 : std::pow((value + 0.055) / 1.055, 2.4); + } + + double EncodeSRGB(double value) + { + return value <= 0.0031308 ? value * 12.92 : 1.055 * std::pow(value, 1.0 / 2.4) - 0.055; + } +} + +TEST(NativeEngineMipmaps, SRGBAndLinearMipChains) +{ + bx::DefaultAllocator allocator; + std::array pixels{}; + for (size_t pixel = 0; pixel < 16; ++pixel) + { + for (size_t channel = 0; channel < 4; ++channel) + { + pixels[pixel * 4 + channel] = pixel % 2 == 0 ? 0 : 255; + } + } + Image source{bimg::imageAlloc(&allocator, bimg::TextureFormat::RGBA8, 4, 4, 0, 1, false, false, pixels.data()), bimg::imageFree}; + ASSERT_NE(source, nullptr); + for (bool srgb : {false, true}) + { + Image output{bimg::imageGenerateMips(&allocator, *source, srgb), bimg::imageFree}; + ASSERT_NE(output, nullptr); + ASSERT_EQ(output->m_numMips, 3); + for (uint8_t level = 0; level < output->m_numMips; ++level) + { + bimg::ImageMip mip{}; + ASSERT_TRUE(bimg::imageGetRawData(*output, 0, level, output->m_data, output->m_size, mip)); + for (uint32_t i = 0; i < mip.m_size; ++i) + { + const uint8_t expected = level == 0 ? pixels[i] : (srgb && i % 4 != 3 ? 188 : 128); + EXPECT_EQ(mip.m_data[i], expected) << "sRGB " << srgb << ", level " << int(level) << ", byte " << i; + } + } + } +} + +TEST(NativeEngineMipmaps, ByteRangeAndTransferFunction) +{ + for (bool srgb : {false, true}) + { + for (int value = 0; value < 256; ++value) + { + std::array pixels{}; + pixels.fill(static_cast(value)); + std::array output{}; + bimg::imageRgba8Downsample2x2(output.data(), 2, 2, 1, 8, 4, pixels.data(), srgb); + for (uint8_t channel : output) + { + EXPECT_EQ(channel, value) << "constant value, sRGB " << srgb; + } + + for (size_t i = 0; i < pixels.size(); ++i) + { + pixels[i] = static_cast((value + i * 53) % 256); + } + bimg::imageRgba8Downsample2x2(output.data(), 2, 2, 1, 8, 4, pixels.data(), srgb); + for (size_t channel = 0; channel < 4; ++channel) + { + const bool decode = srgb && channel != 3; + double average{}; + for (size_t pixel = 0; pixel < 4; ++pixel) + { + const double sample = pixels[pixel * 4 + channel] / 255.0; + average += (decode ? DecodeSRGB(sample) : sample) * 0.25; + } + const auto expected = std::lround((decode ? EncodeSRGB(average) : average) * 255.0); + EXPECT_NEAR(output[channel], expected, 1) << "sRGB " << srgb << ", value " << value << ", channel " << channel; + } + } + } +} diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp index 92aaeb4f9..a1eb6226a 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp @@ -102,11 +102,12 @@ TEST(NativeEngineTextureFormats, D24RenderTargetUsesSupportedBackingStorage) { RunTextureTest([](Napi::Object engine, Napi::Value value) { auto* texture = value.As>().Get(); - for (const uint32_t samples : {1u, 4u}) + for (const uint32_t samples : {1u, 2u, 4u}) { SCOPED_TRACE(samples); texture->Dispose(); - const auto flags = samples == 4 ? BGFX_TEXTURE_RT_MSAA_X4 | BGFX_TEXTURE_MSAA_SAMPLE : BGFX_TEXTURE_RT; + const auto flags = samples == 1 ? BGFX_TEXTURE_RT : + (samples == 2 ? BGFX_TEXTURE_RT_MSAA_X2 : BGFX_TEXTURE_RT_MSAA_X4) | BGFX_TEXTURE_MSAA_SAMPLE; const auto expectedFormat = IsSupported(bgfx::TextureFormat::D24, flags) ? bgfx::TextureFormat::D24 : bgfx::TextureFormat::D24S8; if (!IsSupported(expectedFormat, flags)) @@ -198,3 +199,58 @@ TEST(NativeEngineTextureFormats, RejectedInitializationPreservesExistingTexture) } }); } + +TEST(NativeEngineTextureFormats, SamplingModesSeparateLodClampFromFilterFlags) +{ + RunTextureTest([](Napi::Object engine, Napi::Value value) { + auto* texture = value.As>().Get(); + EXPECT_EQ(texture->SamplerMaxLod(), UINT8_MAX); + constexpr uint32_t preservedFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_COMPARE_LESS; + texture->SamplerFlags(preservedFlags); + + auto setSampling = Napi::Eval(engine.Env(), R"( + (function(engine, texture) { + const stream = new _native.NativeDataStream(function() {}); + engine.setCommandDataStream({_nativeDataStream: stream}); + return function(mode) { + const command = _native.Engine.COMMAND_SETTEXTURESAMPLING; + const words = new Uint32Array(command.length + texture.length + 1); + words.set(command); + words.set(texture, command.length); + words[words.length - 1] = _native.Engine[mode]; + stream.writeBuffer(words.buffer, words.length); + engine.submitCommands(); + }; + }) + )", "native-texture-sampling-test.js").As() + .Call({engine, value}).As(); + + struct SamplingCase + { + const char* Name; + uint32_t Flags; + uint8_t MaxLod; + }; + const SamplingCase cases[]{ + {"TEXTURE_NEAREST_NEAREST", BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIN_POINT, 0}, + {"TEXTURE_NEAREST_NEAREST_MIPNEAREST", BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MIP_POINT, UINT8_MAX}, + {"TEXTURE_LINEAR_LINEAR", 0, 0}, + {"TEXTURE_LINEAR_LINEAR_MIPLINEAR", 0, UINT8_MAX}, + {"TEXTURE_NEAREST_LINEAR", BGFX_SAMPLER_MAG_POINT, 0}, + {"TEXTURE_NEAREST_LINEAR_MIPNEAREST", BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIP_POINT, UINT8_MAX}, + {"TEXTURE_LINEAR_NEAREST", BGFX_SAMPLER_MIN_POINT, 0}, + {"TEXTURE_LINEAR_NEAREST_MIPNEAREST", BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MIP_POINT, UINT8_MAX}, + {"TEXTURE_NEAREST_LINEAR_MIPLINEAR", BGFX_SAMPLER_MAG_POINT, UINT8_MAX}, + {"TEXTURE_NEAREST_NEAREST_MIPLINEAR", BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIN_POINT, UINT8_MAX}, + {"TEXTURE_LINEAR_NEAREST_MIPLINEAR", BGFX_SAMPLER_MIN_POINT, UINT8_MAX}, + {"TEXTURE_LINEAR_LINEAR_MIPNEAREST", BGFX_SAMPLER_MIP_POINT, UINT8_MAX}, + }; + for (const auto& test : cases) + { + SCOPED_TRACE(test.Name); + setSampling.Call({Napi::String::New(engine.Env(), test.Name)}); + EXPECT_EQ(texture->SamplerFlags(), preservedFlags | test.Flags); + EXPECT_EQ(texture->SamplerMaxLod(), test.MaxLod); + } + }); +} diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp index 5e2452b3f..d104e00ec 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp @@ -528,6 +528,8 @@ TEST(NativeEngineClear, ProceduralTextureRetainsBothInputs) #endif } + + TEST(NativeEngineInstanceData, QueuedDrawRetainsDataBeforeUpdate) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) @@ -719,15 +721,11 @@ TEST(ShaderCompilation, FragCoordYMatchesInterpolatedUV) << ", row " << (HEIGHT - 1) << " fragCoord=" << last.first << " uv=" << last.second << std::endl; - // Guard against the whole comparison passing vacuously: the reference ramp - // has to actually sweep the range rather than sitting at a constant. + // Reject a constant ramp that would pass the comparison vacuously. ASSERT_GT(std::abs(first.second - last.second), 200) << "vUV.y reference ramp did not vary across the target"; - // Both channels are produced by the same fragment invocation, so they must - // agree row by row no matter which end of the image the readback starts at. - // The tolerance absorbs interpolation and 8-bit quantization only; a flipped - // gl_FragCoord.y misses by the full range of the ramp. + // Allow interpolation/8-bit quantization, but not a vertical flip. for (uint32_t row = 0; row < HEIGHT; ++row) { const auto values = texel(row); @@ -833,8 +831,7 @@ TEST(ShaderCompilation, FragCoordAndUVAddressATextureIdentically) return static_cast(pixels[static_cast(row) * WIDTH * 4]); }; - // Guard against a vacuous pass: the source must actually vary down the image, - // otherwise a vertical mirror would be undetectable. + // A constant source cannot detect a vertical mirror. ASSERT_GT(std::abs(red(uvPixels, 0) - red(uvPixels, HEIGHT - 1)), 200) << "the source texture must vary from top to bottom for this test to mean anything"; @@ -848,3 +845,61 @@ TEST(ShaderCompilation, FragCoordAndUVAddressATextureIdentically) } #endif } + +TEST(ShaderCompilation, InterfaceBlocksLinkDifferentInstanceNames) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) || !defined(HAS_SHADER_INTERFACE_BLOCKS) + GTEST_SKIP(); +#else + const auto pixels = RenderFullScreenQuad(1, 1, R"( + #extension GL_EXT_shader_io_blocks : require + attribute vec3 position; + out Payload { vec3 tint; } vertexData; + void main() { + gl_Position = vec4(position, 1.0); + vertexData.tint = vec3(0.25, 0.5, 0.75); + } + )", R"( + #extension GL_EXT_shader_io_blocks : require + precision highp float; + in Payload { vec3 tint; } fragmentData; + void main() { gl_FragColor = vec4(fragmentData.tint, 1.0); } + )", false); + ASSERT_EQ(pixels.size(), 4u); + EXPECT_NEAR(pixels[0], 64, 1); + EXPECT_NEAR(pixels[1], 128, 1); + EXPECT_NEAR(pixels[2], 191, 1); + EXPECT_EQ(pixels[3], 255); +#endif +} + +TEST(ShaderCompilation, InterfaceBlocksReserveAllMemberLocations) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) || !defined(HAS_SHADER_INTERFACE_BLOCKS) + GTEST_SKIP(); +#else + const auto pixels = RenderFullScreenQuad(1, 1, R"( + #extension GL_EXT_shader_io_blocks : require + attribute vec3 position; + out Payload { mat2 transform; vec3 tint; } aData; + varying vec3 zExtra; + void main() { + gl_Position = vec4(position, 1.0); + aData.transform = mat2(0.5); + aData.tint = vec3(0.0, 0.0, 0.75); + zExtra = vec3(0.5, 1.0, 0.0); + } + )", R"( + #extension GL_EXT_shader_io_blocks : require + precision highp float; + in Payload { mat2 transform; vec3 tint; } aData; + varying vec3 zExtra; + void main() { gl_FragColor = vec4(aData.transform * zExtra.xy, aData.tint.z + zExtra.z, 1.0); } + )", false); + ASSERT_EQ(pixels.size(), 4u); + EXPECT_NEAR(pixels[0], 64, 1); + EXPECT_NEAR(pixels[1], 128, 1); + EXPECT_NEAR(pixels[2], 191, 1); + EXPECT_EQ(pixels[3], 255); +#endif +} diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp index dcc237fff..316bee087 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -11,22 +12,113 @@ #include #include +#include #include #include #include +#include +#include #include +#include + +#if defined(BABYLON_NATIVE_GRAPHICS_API_D3D11) +#include +#include +#include +#endif #if defined(BABYLON_NATIVE_GRAPHICS_API_VULKAN) #include #include -#include -#include #endif using namespace std::chrono_literals; extern Babylon::Graphics::Configuration g_deviceConfig; +#if defined(BABYLON_NATIVE_GRAPHICS_API_D3D11) || defined(BABYLON_NATIVE_GRAPHICS_API_VULKAN) +namespace +{ + gsl::span ReadUniformFreeShader(const std::vector& bytes) + { + // These fixtures have no uniforms: bgfx v12's code length starts at byte 22. + constexpr size_t codeOffset = 26; + if (bytes.size() < codeOffset || bytes[3] != 12 || bytes[20] != 0 || bytes[21] != 0) + { + throw std::runtime_error{"Expected a uniform-free bgfx v12 shader"}; + } + uint32_t codeSize{}; + std::memcpy(&codeSize, bytes.data() + codeOffset - sizeof(codeSize), sizeof(codeSize)); + if (codeSize > bytes.size() - codeOffset) + { + throw std::runtime_error{"Invalid shader byte length"}; + } + return {bytes.data() + codeOffset, codeSize}; + } +} +#endif + +#if defined(HAS_SHADER_COMPILER) && defined(BABYLON_NATIVE_GRAPHICS_API_D3D11) +TEST(ShaderCompilation, InterfaceBlocksHaveMatchingD3D11Semantics) +{ +#if !defined(HAS_SHADER_INTERFACE_BLOCKS) + GTEST_SKIP() << "Interface blocks require BABYLON_NATIVE_DISABLE_WEBMIN"; +#else + Babylon::Plugins::ShaderCompiler compiler; + const auto shader = compiler.Compile(R"( + #extension GL_EXT_shader_io_blocks : require + precision highp float; + in vec3 position; + out Payload { vec3 tint; } vertexData; + void main() { + gl_Position = vec4(position, 1.0); + vertexData.tint = position; + } + )", R"( + #extension GL_EXT_shader_io_blocks : require + precision highp float; + in Payload { vec3 tint; } fragmentData; + out vec4 color; + void main() { color = vec4(fragmentData.tint, 1.0); } + )"); + const auto readSignature = [](const std::vector& bytes, bool vertex) { + const auto code = ReadUniformFreeShader(bytes); + Microsoft::WRL::ComPtr reflection; + if (FAILED(D3DReflect(code.data(), code.size(), IID_PPV_ARGS(&reflection)))) + { + throw std::runtime_error{"Could not reflect DXBC shader"}; + } + D3D11_SHADER_DESC description{}; + if (FAILED(reflection->GetDesc(&description))) + { + throw std::runtime_error{"Could not read DXBC shader description"}; + } + std::map signature; + const auto count = vertex ? description.OutputParameters : description.InputParameters; + for (UINT i = 0; i < count; ++i) + { + D3D11_SIGNATURE_PARAMETER_DESC parameter{}; + const auto result = vertex ? reflection->GetOutputParameterDesc(i, ¶meter) : reflection->GetInputParameterDesc(i, ¶meter); + if (FAILED(result)) + { + throw std::runtime_error{"Could not read DXBC signature parameter"}; + } + if (std::strcmp(parameter.SemanticName, "TEXCOORD") == 0) + { + EXPECT_TRUE(signature.emplace(parameter.SemanticIndex, parameter.Mask).second); + } + } + return signature; + }; + const auto outputs = readSignature(shader.VertexBytes, true); + const auto inputs = readSignature(shader.FragmentBytes, false); + ASSERT_EQ(outputs.size(), 1u); + ASSERT_EQ(inputs.size(), 1u); + EXPECT_EQ(outputs, inputs); +#endif +} +#endif + #ifdef HAS_SHADER_COMPILER TEST(ShaderCompilation, NativeCompilerAcceptsExistingVec4UniformArray) { @@ -328,20 +420,13 @@ namespace { spirv_cross::Compiler ReadVulkanShader(const std::vector& bytes) { - // These fixtures have no uniforms: bgfx v12's code length starts at byte 22. - constexpr size_t codeOffset = 26; - if (bytes.size() < codeOffset || bytes[3] != 12 || bytes[20] != 0 || bytes[21] != 0) - { - throw std::runtime_error{"Expected a uniform-free bgfx v12 shader"}; - } - uint32_t codeSize{}; - std::memcpy(&codeSize, bytes.data() + codeOffset - sizeof(codeSize), sizeof(codeSize)); - if (codeSize % sizeof(uint32_t) != 0 || codeSize > bytes.size() - codeOffset) + const auto code = ReadUniformFreeShader(bytes); + if (code.size() % sizeof(uint32_t) != 0) { throw std::runtime_error{"Invalid SPIR-V byte length"}; } - std::vector words(codeSize / sizeof(uint32_t)); - std::memcpy(words.data(), bytes.data() + codeOffset, codeSize); + std::vector words(code.size() / sizeof(uint32_t)); + std::memcpy(words.data(), code.data(), code.size()); return spirv_cross::Compiler{std::move(words)}; } diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h index a0454c1dc..eb135d792 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -45,6 +45,8 @@ namespace Babylon::Graphics uint64_t Flags() const; uint32_t SamplerFlags() const; void SamplerFlags(uint32_t); + uint8_t SamplerMaxLod() const; + void SamplerMaxLod(uint8_t); // View sub-range used at bind time to select a single array slice of a multi-layer // texture (bgfx setTexture view API). ViewNumLayers == 0 means "no override; bind whole texture". @@ -91,6 +93,7 @@ namespace Babylon::Graphics bgfx::TextureFormat::Enum m_format{bgfx::TextureFormat::Enum::Unknown}; uint64_t m_flags{BGFX_TEXTURE_NONE}; uint32_t m_samplerFlags{BGFX_SAMPLER_NONE}; + uint8_t m_samplerMaxLod{UINT8_MAX}; uint16_t m_viewFirstLayer{0}; uint16_t m_viewNumLayers{0}; bgfx::ViewId m_blitViewId{UINT16_MAX}; diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index 0323ebbfd..e925dd133 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -327,6 +327,16 @@ namespace Babylon::Graphics m_samplerFlags = value; } + uint8_t Texture::SamplerMaxLod() const + { + return m_samplerMaxLod; + } + + void Texture::SamplerMaxLod(uint8_t value) + { + m_samplerMaxLod = value; + } + uint16_t Texture::ViewFirstLayer() const { return m_viewFirstLayer; diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 52a78d8ca..fb0f3fbfc 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -53,7 +53,9 @@ namespace Babylon constexpr uint32_t SAMPLER_MIN_LINEAR = 0; constexpr uint32_t SAMPLER_MIP_POINT = BGFX_SAMPLER_MIP_POINT; constexpr uint32_t SAMPLER_MIP_LINEAR = 0; - constexpr uint32_t SAMPLER_MIP_IGNORE = BGFX_SAMPLER_MIP_POINT; // HACK: bgfx has no support for ignoring mips + // Native command-stream marker, stripped before passing sampler flags to bgfx. + constexpr uint32_t SAMPLER_MIP_IGNORE = 0x00000800; + static_assert((SAMPLER_MIP_IGNORE & BGFX_SAMPLER_BITS_MASK) == 0); // clang-format off // Names, as in constants.ts are MAG_MIN(_MIP?) MAG MIN MIP @@ -347,7 +349,7 @@ namespace Babylon } bimg::ImageContainer* oldImage{image}; - image = bimg::imageGenerateMips(&allocator, *image); + image = bimg::imageGenerateMips(&allocator, *image, srgb); bimg::imageFree(oldImage); if (image == nullptr) @@ -1734,6 +1736,10 @@ namespace Babylon arcana::trace_region loadRegion{"NativeEngine::LoadTexture"}; bimg::ImageContainer* image{ParseImage(Graphics::DeviceContext::GetDefaultAllocator(), dataSpan)}; image = PrepareImage(Graphics::DeviceContext::GetDefaultAllocator(), image, invertY, srgb, generateMips); + if (image == nullptr) + { + throw std::runtime_error{"Failed to prepare image for texture (unsupported format or image conversion failure)."}; + } LoadTextureFromImage(texture, image, srgb); }) .then(m_runtimeScheduler, *m_cancellationSource, [dataRef{Napi::Persistent(data)}, onSuccessRef{Napi::Persistent(onSuccess)}, onErrorRef{Napi::Persistent(onError)}, cancellationSource{m_cancellationSource}](arcana::expected result) { @@ -1797,6 +1803,7 @@ namespace Babylon const auto format{static_cast(info[4].As().Uint32Value())}; const auto generateMips{info[5].As().Value()}; const auto invertY{info[6].As().Value()}; + const auto srgb{info.Length() > 7 && !info[7].IsUndefined() ? info[7].As().Value() : false}; const auto bytes{static_cast(data.ArrayBuffer().Data()) + data.ByteOffset()}; if (data.ByteLength() != bimg::imageGetSize(nullptr, width, height, 1, false, false, 1, format)) @@ -1810,14 +1817,14 @@ namespace Babylon // escaping here would not be routed to an onError callback. Surface it as a JS error. try { - image = PrepareImage(Graphics::DeviceContext::GetDefaultAllocator(), image, invertY, false, generateMips); + image = PrepareImage(Graphics::DeviceContext::GetDefaultAllocator(), image, invertY, srgb, generateMips); } catch (const std::exception& exception) { throw Napi::Error::New(Env(), exception.what()); } - LoadTextureFromImage(texture, image, false); + LoadTextureFromImage(texture, image, srgb); #endif } @@ -2265,7 +2272,8 @@ namespace Babylon uint32_t flags = texture.SamplerFlags(); flags &= ~(BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIP_POINT); - flags |= value; + flags |= value & ~TextureSampling::SAMPLER_MIP_IGNORE; + texture.SamplerMaxLod((value & TextureSampling::SAMPLER_MIP_IGNORE) != 0 ? 0 : UINT8_MAX); // Disable anisotropy if either min/mag are point. if ((flags & BGFX_SAMPLER_MIN_POINT) != 0 || (flags & BGFX_SAMPLER_MAG_POINT) != 0) @@ -2318,18 +2326,10 @@ namespace Babylon const Graphics::Texture* texture = data.ReadPointer(); bgfx::Encoder* encoder = GetEncoder(); - - const uint16_t numLayers = texture->ViewNumLayers(); - if (numLayers != 0) - { - // Select a single array slice of a multi-layer texture at bind time (e.g. NV12 decoder - // frame-pool slice). The texture itself stays a full TEXTURE2DARRAY. - encoder->setTexture(uniformInfo->Stage, uniformInfo->Handle, texture->Handle(), texture->ViewFirstLayer(), numLayers, 0, UINT8_MAX, texture->SamplerFlags()); - } - else - { - encoder->setTexture(uniformInfo->Stage, uniformInfo->Handle, texture->Handle(), texture->SamplerFlags()); - } + const uint16_t firstLayer = texture->ViewNumLayers() != 0 ? texture->ViewFirstLayer() : 0; + const uint16_t numLayers = texture->ViewNumLayers() != 0 ? texture->ViewNumLayers() : UINT16_MAX; + encoder->setTexture(uniformInfo->Stage, uniformInfo->Handle, texture->Handle(), + firstLayer, numLayers, 0, UINT8_MAX, texture->SamplerFlags(), 0, texture->SamplerMaxLod()); } void NativeEngine::UnsetTexture(NativeDataStream::Reader& data) diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp index 6699a1c74..135a534a1 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp @@ -111,6 +111,7 @@ namespace Babylon::Plugins std::map vertexAttributeRenaming = {}; auto builtInInstanceDataSlots = ShaderCompilerTraversers::AssignLocationsAndNamesToVertexVaryingsD3D(program, ids, vertexAttributeRenaming, instancedAttributes); ShaderCompilerTraversers::FlattenNarrowVaryingArrays(program, ids); + ShaderCompilerTraversers::AssignInterStageVaryingLocations(program, ids); ShaderCompilerTraversers::SplitSamplersIntoSamplersAndTextures(program, ids); ShaderCompilerTraversers::SplitSamplerFunctionParameters(program, ids); ShaderCompilerTraversers::ZeroInitializeStructLocals(program); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp index 6cc12206a..9ad3ffcde 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp @@ -186,6 +186,7 @@ namespace Babylon::Plugins auto utstScope = ShaderCompilerTraversers::MoveNonSamplerUniformsIntoStruct(program, ids); std::map vertexAttributeRenaming = {}; auto builtInInstanceDataSlots = ShaderCompilerTraversers::AssignLocationsAndNamesToVertexVaryingsD3D(program, ids, vertexAttributeRenaming, instancedAttributes); + ShaderCompilerTraversers::AssignInterStageVaryingLocations(program, ids); ShaderCompilerTraversers::SplitSamplersIntoSamplersAndTextures(program, ids); ShaderCompilerTraversers::SplitSamplerFunctionParameters(program, ids); ShaderCompilerTraversers::ZeroInitializeStructLocals(program); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp index ff55ad54d..4eed0aa31 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp @@ -115,6 +115,7 @@ namespace Babylon::Plugins auto utstScope = ShaderCompilerTraversers::MoveNonSamplerUniformsIntoStruct(program, ids); std::map vertexAttributeRenaming = {}; auto builtInInstanceDataSlots = ShaderCompilerTraversers::AssignLocationsAndNamesToVertexVaryingsMetal(program, ids, vertexAttributeRenaming, instancedAttributes); + ShaderCompilerTraversers::AssignInterStageVaryingLocations(program, ids); ShaderCompilerTraversers::SplitSamplersIntoSamplersAndTextures(program, ids); ShaderCompilerTraversers::SplitSamplerFunctionParameters(program, ids); ShaderCompilerTraversers::ZeroInitializeStructLocals(program); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp index fe0e9b602..35a21dd5d 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp @@ -2529,6 +2529,116 @@ namespace Babylon::ShaderCompilerTraversers TIntermediate* m_intermediate{}; std::vector> m_symbolsToParents{}; }; + + /// Assign matching locations across stages; mutate symbols in place to retain single-parent ASTs. + class InterStageVaryingLocationTraverser : private TIntermTraverser + { + public: + static void Traverse(TProgram& program, IdGenerator& /*ids*/) + { + auto* vs = program.getIntermediate(EShLangVertex); + auto* fs = program.getIntermediate(EShLangFragment); + if (vs == nullptr || fs == nullptr) + { + return; + } + + InterStageVaryingLocationTraverser vsCollector{EvqVaryingOut}; + vs->getTreeRoot()->traverse(&vsCollector); + InterStageVaryingLocationTraverser fsCollector{EvqVaryingIn}; + fs->getTreeRoot()->traverse(&fsCollector); + + // Pack fragment-read varyings first in both stages. Leading vertex-only slots can break D3D linkage. + std::map sharedNameToType{}; + std::map vsOnlyNameToType{}; + for (const auto& [name, symbol] : fsCollector.m_linkerSymbols) + { + sharedNameToType[name] = &symbol->getType(); + } + for (const auto& [name, symbol] : vsCollector.m_linkerSymbols) + { + if (sharedNameToType.find(name) == sharedNameToType.end()) + { + vsOnlyNameToType[name] = &symbol->getType(); + } + } + + if (sharedNameToType.empty() && vsOnlyNameToType.empty()) + { + return; + } + + std::map nameToLocation{}; + unsigned int nextLocation = 0; + for (const auto& [name, type] : sharedNameToType) + { + nameToLocation[name] = nextLocation; + nextLocation += TIntermediate::computeTypeLocationSize(*type, EShLangFragment); + } + for (const auto& [name, type] : vsOnlyNameToType) + { + nameToLocation[name] = nextLocation; + nextLocation += TIntermediate::computeTypeLocationSize(*type, EShLangFragment); + } + + ApplyLocations(nameToLocation, vsCollector.m_symbols); + ApplyLocations(nameToLocation, fsCollector.m_symbols); + } + + private: + explicit InterStageVaryingLocationTraverser(TStorageQualifier storage) + : TIntermTraverser{} + , m_storage{storage} + { + } + + void visitSymbol(TIntermSymbol* symbol) override + { + if (symbol->getType().getQualifier().storage != m_storage) + { + return; + } + + // Skip built-ins (gl_Position, gl_PointSize, etc.) — they use dedicated semantics. + if (symbol->getType().getQualifier().builtIn != EbvNone) + { + return; + } + + m_symbols.push_back(symbol); + if (IsLinkerObject(this->path)) + { + m_linkerSymbols[InterfaceName(*symbol).c_str()] = symbol; + } + } + + static const TString& InterfaceName(const TIntermSymbol& symbol) + { + // Block instance names are stage-local; the type identifies the shared interface. + const auto& type = symbol.getType(); + return type.getBasicType() == EbtBlock ? type.getTypeName() : symbol.getName(); + } + + static void ApplyLocations( + const std::map& nameToLocation, + const std::vector& symbols) + { + for (TIntermSymbol* symbol : symbols) + { + const std::string name = InterfaceName(*symbol).c_str(); + const auto it = nameToLocation.find(name); + if (it == nameToLocation.end()) + { + continue; + } + symbol->getWritableType().getQualifier().layoutLocation = it->second; + } + } + + TStorageQualifier m_storage{}; + std::map m_linkerSymbols{}; + std::vector m_symbols{}; + }; } ScopeT MoveNonSamplerUniformsIntoStruct(TProgram& program, IdGenerator& ids) @@ -2576,6 +2686,11 @@ namespace Babylon::ShaderCompilerTraversers NarrowVaryingArrayFlattenerTraverser::Traverse(program, ids); } + void AssignInterStageVaryingLocations(TProgram& program, IdGenerator& ids) + { + InterStageVaryingLocationTraverser::Traverse(program, ids); + } + void InvertYDerivativeOperands(TProgram& program) { InvertYDerivativeOperandsTraverser::Traverse(program); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h index bab811bda..049eb4829 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h @@ -160,24 +160,18 @@ namespace Babylon::ShaderCompilerTraversers /// Only needed for the fxc (DXBC) backend; dxc accepts the indexable range. void FlattenNarrowVaryingArrays(glslang::TProgram& program, IdGenerator& ids); + /// Match varyings by name and interface blocks by type, shared before vertex-only outputs. + /// Reserve complete matrix, array and block-member ranges. Run after FlattenNarrowVaryingArrays. + void AssignInterStageVaryingLocations(glslang::TProgram& program, IdGenerator& ids); + /// Invert dFdy operands similar to bgfx_shader.sh /// https://github.com/bkaradzic/bgfx/blob/7be225bf490bb1cd231cfb4abf7e617bf35b59cb/src/bgfx_shader.sh#L44-L45 /// https://github.com/bkaradzic/bgfx/blob/7be225bf490bb1cd231cfb4abf7e617bf35b59cb/src/bgfx_shader.sh#L62-L65 void InvertYDerivativeOperands(glslang::TProgram& program); - /// Flip the vertical (V) component of 2D texture sample coordinates, i.e. rewrite the - /// coordinate `uv` of every `texture()`/`textureLod()` call to `vec2(uv.x, 1.0 - uv.y)`. - /// - /// bgfx's D3D/Metal/Vulkan backends sample textures with the opposite V-orientation from - /// WebGL/OpenGL. This was historically corrected by injecting a `#define texture(x,y) - /// texture(x, flip(y))` preprocessor macro into the shader source, but a 2-argument - /// function-like macro cannot match the 3-argument bias form `texture(sampler, uv, bias)` - /// that some Babylon.js shaders emit (e.g. GreasedLine), and glslang's preprocessor has no - /// variadic-macro support. Performing the flip on the AST handles every texture()/textureLod() - /// arity and sampler type. Only 2-component (sampler2D-style) coordinates are flipped, matching - /// the previous `flip(vec2)`/`flip(vec3)` overloads where vec3+ coordinates were left untouched. - /// Must only be used on the backends that apply ProcessSamplerFlip (D3D, Metal, Vulkan); the - /// OpenGL backend shares bgfx's V-orientation and must not flip. + /// Flip only texture-coordinate Y on D3D/Metal/Vulkan, including shadow, array, and volume samplers. + /// For texelFetch, use the selected mip height. Preserve all other components and cube directions. + /// Do not apply on OpenGL. void FlipSamplerCoordinates(glslang::TProgram& program); /// Convert fragment coordinates with bnFragCoordTargetSize on D3D/Metal/Vulkan. From 6d0b102aaa246bb4dad0d2b56cd3ac65b034e82e Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:26 -0700 Subject: [PATCH 11/16] Support MRT clears and shared framebuffer attachments Add multiple-render-target attachment routing, shared sampleable depth, layer/mip selection, and explicit texture-handle ownership. Use floating-point clear colors and additional blend equations for HDR rendering, preserving float readbacks and manually authored mip levels. Migrate clear callers and retain upstream MSAA initialization with the correct framebuffer sample metadata. Keep protocol-9 clear and stencil payloads; defer the new mask fields. Review-Group: H2a Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 8007d43a58afbc67d136b061c8cc2ccea3a78499 Split-Part: code --- .../Tests.Device.D3D11.ExternalBackBuffer.cpp | 4 +- .../Source/Tests.Device.FrameEncoder.cpp | 94 +++++++ .../Source/Tests.Device.SwapChain.cpp | 6 +- Apps/UnitTests/Source/Tests.Device.cpp | 3 +- .../Tests.NativeEngine.CubeRenderTargets.cpp | 7 +- .../Tests.NativeEngine.DepthTextures.cpp | 24 +- .../Source/Tests.NativeEngine.Msaa.cpp | 4 +- .../Babylon/Graphics/DeviceContext.h | 2 + .../Babylon/Graphics/FrameBuffer.h | 4 +- .../Babylon/Graphics/Texture.h | 4 +- Core/Graphics/Source/DeviceContext.cpp | 5 + Core/Graphics/Source/DeviceImpl.cpp | 32 +++ Core/Graphics/Source/DeviceImpl.h | 6 + Core/Graphics/Source/FrameBuffer.cpp | 62 +++- Core/Graphics/Source/Texture.cpp | 9 +- Plugins/NativeEngine/Source/NativeEngine.cpp | 264 +++++++++++++++--- Plugins/NativeEngine/Source/NativeEngine.h | 3 +- Plugins/NativeXr/Source/NativeXrImpl.cpp | 2 +- Polyfills/Canvas/Source/Canvas.cpp | 2 +- Polyfills/Canvas/Source/Context.cpp | 4 +- 20 files changed, 474 insertions(+), 67 deletions(-) diff --git a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp index da23654a2..b8841eeb1 100644 --- a/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp +++ b/Apps/UnitTests/Source/Tests.Device.D3D11.ExternalBackBuffer.cpp @@ -181,7 +181,9 @@ namespace { device.StartRenderingCurrentFrame(); Babylon::Graphics::FrameBuffer backBuffer{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; - backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, color, 1.0f, 0); + backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, + static_cast(color >> 24) / 255.0f, static_cast(color >> 16) / 255.0f, + static_cast(color >> 8) / 255.0f, static_cast(color) / 255.0f, 1.0f, 0); device.FinishRenderingCurrentFrame(); } if (!captured->has_value()) diff --git a/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp b/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp index c04303001..a8b506656 100644 --- a/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp +++ b/Apps/UnitTests/Source/Tests.Device.FrameEncoder.cpp @@ -4,12 +4,15 @@ #include #include #include +#include #ifdef HAS_TEST_UTILS #include #endif #include +#include #include +#include #include extern Babylon::Graphics::Configuration g_deviceConfig; @@ -140,6 +143,97 @@ TEST(Device, FrameBufferMultisamplingTracksItsOwnTarget) device.FinishRenderingCurrentFrame(); } +TEST(Device, ClearPalettePreservesPendingColorsAndResetsAfterSubmission) +{ + Babylon::Graphics::Device device{g_deviceConfig}; +#if defined(USE_NOOP_METAL_DEVICE) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP() << "GPU rendering/readback is unavailable in this test configuration"; +#endif + device.StartRenderingCurrentFrame(); + if (!bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::RGBA32F, BGFX_TEXTURE_RT)) + { + GTEST_SKIP() << "RGBA32F render targets are unavailable"; + } + constexpr uint16_t width = 40; + std::array, width> colors{}; + for (uint16_t x = 0; x < width; ++x) + { + colors[x] = x < 24 ? std::array{float(x) / 255.f, float(x * 3) / 255.f, 0.f, 1.f} + : std::array{float(x), -0.5f, 0.125f, 1.f}; + } + std::array pixels{}; + std::promise completed; + auto future = completed.get_future(); + Babylon::AppRuntime runtime{}; + runtime.Dispatch([&](Napi::Env env) { + try + { + device.AddToJavaScript(env); + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + auto scope = context.AcquireFrameCompletionScope(); + Babylon::Graphics::Texture target{context}; + target.Create2D(width, 1, false, 1, bgfx::TextureFormat::RGBA32F, BGFX_TEXTURE_RT); + const auto handle = target.Handle(); + Babylon::Graphics::FrameBuffer frameBuffer{context, bgfx::createFrameBuffer(1, &handle), width, 1, false, false, false}; + for (uint16_t x = 0; x < width; ++x) + { + frameBuffer.SetScissor(float(x), 0, 1, 1); + const auto& color = colors[x]; + frameBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, color[0], color[1], color[2], color[3], 1.f, 0); + } + const auto& last = colors.back(); + EXPECT_NO_THROW(frameBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, last[0], last[1], last[2], last[3], 1.f, 0)); + EXPECT_THROW(frameBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, 100.f, 0.f, 0.f, 1.f, 1.f, 0), std::runtime_error); + auto readback = std::make_shared(context); + readback->Create2D(width, 1, false, 1, bgfx::TextureFormat::RGBA32F, BGFX_TEXTURE_READ_BACK); + bgfx::TextureRegion source{}; + source.init(handle, 0, 0, width, 1); + auto destination = source; + destination.handle = readback->Handle(); + context.GetActiveEncoder()->blit(context.AcquireNewViewId(), destination, source); + context.ReadTextureAsync(readback->Handle(), gsl::make_span(reinterpret_cast(pixels.data()), sizeof(pixels))) + .then(arcana::inline_scheduler, arcana::cancellation::none(), [readback, &completed](arcana::expected result) { + if (result.has_error()) + { + completed.set_exception(result.error()); + } + else + { + completed.set_value(); + } + }); + } + catch (...) + { + completed.set_exception(std::current_exception()); + } + }); + while (future.wait_for(std::chrono::milliseconds{16}) != std::future_status::ready) + { + device.FinishRenderingCurrentFrame(); + device.StartRenderingCurrentFrame(); + } + EXPECT_NO_THROW(future.get()); + for (size_t x = 0; x < width; ++x) + { + for (size_t channel = 0; channel < 4; ++channel) + { + EXPECT_NEAR(pixels[x * 4 + channel], colors[x][channel], 0.000001f) << x << ", " << channel; + } + } + device.FinishRenderingCurrentFrame(); + RunOnRuntimeThread(runtime, [](Napi::Env env) { + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + EXPECT_EQ(context.AcquireClearPaletteIndex({100.f, 0.f, 0.f, 1.f}), 0); + }); + device.DisableRendering(); + device.EnableRendering(); + RunOnRuntimeThread(runtime, [](Napi::Env env) { + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + EXPECT_EQ(context.AcquireClearPaletteIndex({200.f, 0.f, 0.f, 1.f}), 0); + }); +} + // The guarantee the fix relies on: acquiring a FrameCompletionScope blocks until a frame is live, // and once acquired the encoder is valid and cannot be ended underneath the holder. TEST(Device, FrameCompletionScopeProvidesEncoderOutsideFrame) diff --git a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp index 9f1361007..c9fb0f18c 100644 --- a/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp +++ b/Apps/UnitTests/Source/Tests.Device.SwapChain.cpp @@ -50,7 +50,9 @@ namespace { device.StartRenderingCurrentFrame(); Babylon::Graphics::FrameBuffer backBuffer{context, BGFX_INVALID_HANDLE, 0, 0, true, true, true}; - backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, color, 1.0f, 0); + backBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, + static_cast(color >> 24) / 255.0f, static_cast(color >> 16) / 255.0f, + static_cast(color >> 8) / 255.0f, static_cast(color) / 255.0f, 1.0f, 0); if (render) { render(*context.GetActiveEncoder()); @@ -104,7 +106,7 @@ TEST(Device, ExplicitDefaultFrameBufferPreservesTarget) EXPECT_NE(target.Handle().idx, window.Handle().idx); ExpectSolidColor(ClearAndCapture(device, context, 0xff0000ff, [&](bgfx::Encoder& encoder) { - target.Clear(encoder, BGFX_CLEAR_COLOR, 0x00ff00ff, 1.0f, 0); + target.Clear(encoder, BGFX_CLEAR_COLOR, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0); }), 32, 24, 0xff0000ff); target.Dispose(); diff --git a/Apps/UnitTests/Source/Tests.Device.cpp b/Apps/UnitTests/Source/Tests.Device.cpp index 4256dc31c..5425c1157 100644 --- a/Apps/UnitTests/Source/Tests.Device.cpp +++ b/Apps/UnitTests/Source/Tests.Device.cpp @@ -201,7 +201,8 @@ TEST(Device, DeviceLossCancelsReadbacksBeforeRecovery) { device.StartRenderingCurrentFrame(); FrameBuffer backBuffer{device.GetContext(), BGFX_INVALID_HANDLE, 0, 0, true, true, true}; - backBuffer.Clear(*device.GetActiveEncoder(), BGFX_CLEAR_COLOR, 0x123456ff, 1.0f, 0); + backBuffer.Clear(*device.GetActiveEncoder(), BGFX_CLEAR_COLOR, + 18.0f / 255.0f, 52.0f / 255.0f, 86.0f / 255.0f, 1.0f, 1.0f, 0); device.FinishRenderingCurrentFrame(); } EXPECT_EQ(recoveredScreenshots, 1u); diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp index c47ec67c2..370df975b 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp @@ -88,7 +88,12 @@ TEST(NativeEngineCubeRenderTargets, ClearsEachFaceIndependentlyAndPreserves2DDef Napi::Number::New(env, 1), Napi::Number::New(env, face)}); frameBuffers.Set(face, frameBufferValue); auto* frameBuffer = frameBufferValue.As>().Get(); - frameBuffer->Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, colors[face], 1.0f, 0); + const auto color = colors[face]; + frameBuffer->Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, + static_cast((color >> 24) & 0xff) / 255.0f, + static_cast((color >> 16) & 0xff) / 255.0f, + static_cast((color >> 8) & 0xff) / 255.0f, + static_cast(color & 0xff) / 255.0f, 1.0f, 0); } const auto expectInvalidLayer = [&](Napi::Value texture, double layer) { SCOPED_TRACE(layer); diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.DepthTextures.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.DepthTextures.cpp index a8ffae256..6c6575468 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.DepthTextures.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.DepthTextures.cpp @@ -83,11 +83,33 @@ TEST(NativeEngineDepthTextures, ExposesReadableDepthAndPreservesFramebufferOwner 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); + BGFX_CLEAR_DEPTH | (stencil ? BGFX_CLEAR_STENCIL : 0), 0, 0, 0, 0, 0.25f, 0); frameBuffer->Dispose(); } } } + { + auto value = engine.Get("createTexture").As().Call(engine, {}); + auto* texture = value.As>().Get(); + const auto createSharedDepthFrameBuffer = [&] { + return engine.Get("createMultiFrameBuffer").As().Call(engine, { + Napi::Array::New(env), Napi::Number::New(env, 16), Napi::Number::New(env, 16), + Napi::Boolean::New(env, false), Napi::Boolean::New(env, true), Napi::Number::New(env, 1), + env.Undefined(), value}); + }; + auto firstValue = createSharedDepthFrameBuffer(); + auto secondValue = createSharedDepthFrameBuffer(); + auto* first = firstValue.As>().Get(); + auto* second = secondValue.As>().Get(); + EXPECT_EQ(texture->Handle().idx, bgfx::getTexture(first->Handle(), 0).idx); + EXPECT_EQ(texture->Handle().idx, bgfx::getTexture(second->Handle(), 0).idx); + first->Dispose(); + // Shared depth remains texture-owned when one of its framebuffers is disposed. + second->Clear(*context.GetActiveEncoder(), BGFX_CLEAR_DEPTH, 0, 0, 0, 0, 0.25f, 0); + second->Dispose(); + EXPECT_TRUE(texture->IsValid()); + texture->Dispose(); + } auto frameBufferValue = createFrameBuffer(env.Null(), false, true); auto* frameBuffer = frameBufferValue.As>().Get(); EXPECT_TRUE(frameBuffer->HasDepth()); diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.Msaa.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.Msaa.cpp index c069b7b7d..4fba47ded 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.Msaa.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.Msaa.cpp @@ -241,8 +241,8 @@ TEST(NativeEngineMsaa, PreservesSampleCountsAndAllocatesSampledStorage) Babylon::Graphics::Texture target{context}; target.Create2D(16, 16, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); auto handle = target.Handle(); - Babylon::Graphics::FrameBuffer frameBuffer{context, bgfx::createFrameBuffer(1, &handle), 16, 16, false, false, false}; - frameBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, 0, 0.0f, 0); + Babylon::Graphics::FrameBuffer frameBuffer{context, bgfx::createFrameBuffer(1, &handle), 16, 16, false, false, false, -1, false}; + frameBuffer.Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); const auto beforeInitialization = context.PeekNextViewId(); Babylon::Graphics::Texture inserted{context}; inserted.Create2D(16, 16, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT_MSAA_X2); diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h index 73ea0bd93..fb5c67d8c 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/DeviceContext.h @@ -10,6 +10,7 @@ #include +#include #include #include @@ -129,6 +130,7 @@ namespace Babylon::Graphics // view id that is retained across draw calls and re-acquire when it changes. uint32_t ViewIdGeneration() const; bgfx::VertexLayoutHandle CreateVertexLayout(const bgfx::VertexLayout& layout); + uint8_t AcquireClearPaletteIndex(const std::array& color); // If the current frame is close to exhausting bgfx views, flush accumulated // views (cross-thread bgfx::frame + view-counter reset) so rendering can diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h index a2b478a16..9bfdd68b2 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h @@ -37,7 +37,9 @@ namespace Babylon::Graphics void Bind(); void Unbind(); - void Clear(bgfx::Encoder& encoder, uint16_t flags, uint32_t rgba, float depth, uint8_t stencil); + // Floating-point colors preserve HDR values through bgfx's clear palette. + // More than 16 distinct palette colors in a physical frame throws rather than overwriting pending clears. + void Clear(bgfx::Encoder& encoder, uint16_t flags, float r, float g, float b, float a, float depth, uint8_t stencil, uint8_t colorAttachmentMask = UINT8_MAX); void SetViewPort(float x, float y, float width, float height); void SetScissor(float x, float y, float width, float height); void Submit(bgfx::Encoder& encoder, bgfx::ProgramHandle programHandle, uint8_t flags); diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h index eb135d792..b6bedab66 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/Texture.h @@ -31,7 +31,9 @@ namespace Babylon::Graphics void CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags); void UpdateCube(uint16_t layer, uint8_t side, uint8_t mip, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const bgfx::Memory* mem, uint16_t pitch = UINT16_MAX); - void Attach(bgfx::TextureHandle handle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags); + // ownsHandle=true: Texture destroys the handle on Dispose (e.g. sampleable depth aliased from an FB). + // ownsHandle=false: handle is owned elsewhere (framebuffer, external texture, canvas FB). + void Attach(bgfx::TextureHandle handle, bool ownsHandle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags); bgfx::TextureHandle Handle() const; uint16_t Width() const; diff --git a/Core/Graphics/Source/DeviceContext.cpp b/Core/Graphics/Source/DeviceContext.cpp index 3c75df894..e652b4d8d 100644 --- a/Core/Graphics/Source/DeviceContext.cpp +++ b/Core/Graphics/Source/DeviceContext.cpp @@ -157,6 +157,11 @@ namespace Babylon::Graphics return m_graphicsImpl.CreateVertexLayout(layout); } + uint8_t DeviceContext::AcquireClearPaletteIndex(const std::array& color) + { + return m_graphicsImpl.AcquireClearPaletteIndex(color); + } + void DeviceContext::FlushViewsIfNeeded() { m_graphicsImpl.FlushViewsIfNeeded(); diff --git a/Core/Graphics/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index 2bd1e293b..e278e6b87 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -308,6 +308,7 @@ namespace Babylon::Graphics } m_state.Bgfx.Initialized = true; + ResetClearPalette(); UpdateBackBufferState(); m_cancellationSource.emplace(); @@ -666,6 +667,32 @@ namespace Babylon::Graphics m_frameVertexLayouts.clear(); } + void DeviceImpl::ResetClearPalette() + { + std::scoped_lock lock{m_clearPaletteMutex}; + m_clearPaletteSize = 0; + } + + uint8_t DeviceImpl::AcquireClearPaletteIndex(const std::array& color) + { + std::scoped_lock lock{m_clearPaletteMutex}; + for (uint8_t index = 0; index < m_clearPaletteSize; ++index) + { + if (m_clearPalette[index] == color) + { + return index; + } + } + if (m_clearPaletteSize == m_clearPalette.size()) + { + throw std::runtime_error{"Too many distinct floating-point clear colors in one physical frame"}; + } + const auto index = m_clearPaletteSize++; + m_clearPalette[index] = color; + bgfx::setPaletteColor(index, color.data()); + return index; + } + void DeviceImpl::FlushViewsIfNeeded() { // Reserve headroom below the hard cap: a single draw/clear operation can @@ -768,6 +795,7 @@ namespace Babylon::Graphics const uint32_t frameNumber{bgfx::frame(BGFX_FRAME_FLUSH)}; CompleteReadTextureRequests(frameNumber); + ResetClearPalette(); m_nextViewId.store(0); m_midFrameFlushCount.fetch_add(1); @@ -787,6 +815,7 @@ namespace Babylon::Graphics // Discard the whole frame. ReleaseFrameVertexLayouts(); bgfx::frame(BGFX_FRAME_DISCARD); + ResetClearPalette(); if (m_bgfxCallback.IsDeviceLost()) { return; @@ -843,6 +872,7 @@ namespace Babylon::Graphics // Release the old native swap chain before another one can bind its window. ReleaseFrameVertexLayouts(); bgfx::frame(BGFX_FRAME_DISCARD); + ResetClearPalette(); if (m_state.BackBufferColor || m_state.BackBufferDepthStencil) { CreateExternalBackBuffer(swapChain); @@ -861,6 +891,7 @@ namespace Babylon::Graphics DestroyBackBuffer(); ReleaseFrameVertexLayouts(); bgfx::frame(BGFX_FRAME_DISCARD); + ResetClearPalette(); } if (swapChain.nwh != nullptr) @@ -947,6 +978,7 @@ namespace Babylon::Graphics CompleteReadTextureRequests(frameNumber); + ResetClearPalette(); m_nextViewId.store(0); m_midFrameFlushCount.store(0); } diff --git a/Core/Graphics/Source/DeviceImpl.h b/Core/Graphics/Source/DeviceImpl.h index b9c16b5e5..ad82c24c7 100644 --- a/Core/Graphics/Source/DeviceImpl.h +++ b/Core/Graphics/Source/DeviceImpl.h @@ -105,6 +105,7 @@ namespace Babylon::Graphics bgfx::ViewId PeekNextViewId() const; uint32_t ViewIdGeneration() const; bgfx::VertexLayoutHandle CreateVertexLayout(const bgfx::VertexLayout& layout); + uint8_t AcquireClearPaletteIndex(const std::array& color); // Mid-frame view flush. If the current logical frame has acquired close to // the maximum number of bgfx views, flush the accumulated views via a @@ -156,6 +157,7 @@ namespace Babylon::Graphics void DestroyBackBuffer(); bool RequestScreenShots(); void Frame(); + void ResetClearPalette(); void CompleteReadTextureRequests(uint32_t frameNumber); void PerformMidFrameViewFlush(); void CaptureCallback(const BgfxCallback::CaptureData&); @@ -198,6 +200,10 @@ namespace Babylon::Graphics std::mutex m_frameVertexLayoutsMutex{}; std::map m_frameVertexLayouts{}; + std::mutex m_clearPaletteMutex{}; + std::array, 16> m_clearPalette{}; + uint8_t m_clearPaletteSize{}; + // Number of mid-frame view flushes performed during the current logical frame; reset // when the frame is actually presented. The flush lets a logical frame exceed bgfx's // per-frame view budget, but each one is a blocking round-trip to the render thread, diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index 2a90297d4..b67881900 100644 --- a/Core/Graphics/Source/FrameBuffer.cpp +++ b/Core/Graphics/Source/FrameBuffer.cpp @@ -3,6 +3,44 @@ #include #include +namespace +{ + // setViewClear accepts eight attachment indices. + constexpr uint8_t MaxColorAttachments{8}; + + std::optional PackClearColor(const std::array& color) + { + uint32_t packed{}; + for (const float component : color) + { + if (!std::isfinite(component) || component < 0 || component > 1) + { + return std::nullopt; + } + const auto byte = static_cast(std::round(component * 255.f)); + if (static_cast(byte) / 255.f != component) + { + return std::nullopt; + } + packed = (packed << 8) | byte; + } + return packed; + } + + // Only D3D11/12 treat UINT8_MAX as an attachment skip; other backends clamp it into the palette. + bool SupportsClearAttachmentMasking() + { + switch (bgfx::getRendererType()) + { + case bgfx::RendererType::Direct3D11: + case bgfx::RendererType::Direct3D12: + return true; + default: + return false; + } + } +} + namespace Babylon::Graphics { FrameBuffer::FrameBuffer(DeviceContext& deviceContext, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex, bool isMultisampled) @@ -87,14 +125,34 @@ namespace Babylon::Graphics { } - void FrameBuffer::Clear(bgfx::Encoder& encoder, uint16_t flags, uint32_t rgba, float depth, uint8_t stencil) + void FrameBuffer::Clear(bgfx::Encoder& encoder, uint16_t flags, float r, float g, float b, float a, float depth, uint8_t stencil, uint8_t colorAttachmentMask) { + const bool maskColorAttachments{colorAttachmentMask != UINT8_MAX && bgfx::isValid(m_handle) && SupportsClearAttachmentMasking()}; + const std::array color{r, g, b, a}; + const auto packedColor = maskColorAttachments ? std::nullopt : PackClearColor(color); + const bool usePalette = (flags & BGFX_CLEAR_COLOR) != 0 && !packedColor.has_value(); + const auto paletteIndex = usePalette ? m_deviceContext.AcquireClearPaletteIndex(color) : uint8_t{}; + // BGFX requires us to create a new viewID, this will ensure that the view gets cleared. m_viewId = m_deviceContext.AcquireNewViewId(); m_viewIdGeneration = m_deviceContext.ViewIdGeneration(); bgfx::setViewMode(m_viewId.value(), bgfx::ViewMode::Sequential); - bgfx::setViewClear(m_viewId.value(), flags, rgba, depth, stencil); + // Float palette entries preserve HDR clears and per-attachment masks. + if (usePalette) + { + uint8_t indices[MaxColorAttachments]; + for (uint8_t attachment = 0; attachment < MaxColorAttachments; ++attachment) + { + indices[attachment] = !maskColorAttachments || (colorAttachmentMask & (1 << attachment)) != 0 ? paletteIndex : UINT8_MAX; + } + bgfx::setViewClear(m_viewId.value(), flags, depth, stencil, + indices[0], indices[1], indices[2], indices[3], indices[4], indices[5], indices[6], indices[7]); + } + else + { + bgfx::setViewClear(m_viewId.value(), flags, packedColor.value_or(0), depth, stencil); + } bgfx::setViewFrameBuffer(m_viewId.value(), Handle()); // If a scissor is not set, WebGL clears the entire screen, so set the view rect to cover the entire screen diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index e925dd133..5476c232f 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -111,8 +111,8 @@ namespace { throw std::runtime_error{"Failed to create render-target initialization framebuffer"}; } - Babylon::Graphics::FrameBuffer frameBuffer{context, frameBufferHandle, mipWidth, mipHeight, false, depth, stencil}; - frameBuffer.Clear(*context.GetActiveEncoder(), clearFlags, 0, 0.0f, 0); + Babylon::Graphics::FrameBuffer frameBuffer{context, frameBufferHandle, mipWidth, mipHeight, false, depth, stencil, -1, multisampled}; + frameBuffer.Clear(*context.GetActiveEncoder(), clearFlags, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0); } } @@ -256,14 +256,13 @@ namespace Babylon::Graphics bgfx::updateTextureCube(m_handle, layer, side, mip, x, y, width, height, mem, pitch); } - void Texture::Attach(bgfx::TextureHandle handle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) + void Texture::Attach(bgfx::TextureHandle handle, bool ownsHandle, uint16_t width, uint16_t height, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) { Dispose(); assert(bgfx::isValid(handle)); m_handle = handle; - - m_ownsHandle = false; + m_ownsHandle = ownsHandle; SetMetadata(width, height, 0, hasMips, false, false, numLayers, format, flags); } diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index fb0f3fbfc..1a378d3dd 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -17,6 +17,13 @@ #include #include +#include + +namespace bgfx +{ + // Internal bgfx API used to report framebuffer validation failures. + void isFrameBufferValid(uint8_t _num, const Attachment* _attachment, bx::Error* _err); +} #ifdef BABYLON_NATIVE_PLUGIN_NATIVEENGINE_LOAD_IMAGES #include @@ -91,6 +98,39 @@ namespace Babylon constexpr uint64_t SCREENMODE = BGFX_STATE_BLEND_FUNC_SEPARATE(BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_COLOR, BGFX_STATE_BLEND_ONE, BGFX_STATE_BLEND_INV_SRC_ALPHA); } + // Matches BABYLON.Constants.ALPHA_EQUATION_* (not bgfx's internal equation indices). + namespace AlphaEquation + { + constexpr uint32_t ADD = 0; + constexpr uint32_t SUBTRACT = 1; + constexpr uint32_t REVERSE_SUBTRACT = 2; + constexpr uint32_t MAX = 3; + constexpr uint32_t MIN = 4; + constexpr uint32_t DARKEN = 5; + } + + // Normalize floating-point readbacks to RGBA32F to preserve depth/HDR values. + constexpr bool IsFloatTextureFormat(bgfx::TextureFormat::Enum format) + { + switch (format) + { + case bgfx::TextureFormat::R16F: + case bgfx::TextureFormat::RG16F: + case bgfx::TextureFormat::RGBA16F: + case bgfx::TextureFormat::R32F: + case bgfx::TextureFormat::RG32F: + case bgfx::TextureFormat::RGBA32F: + case bgfx::TextureFormat::RGB9E5F: + case bgfx::TextureFormat::RG11B10F: + case bgfx::TextureFormat::D16F: + case bgfx::TextureFormat::D24F: + case bgfx::TextureFormat::D32F: + return true; + default: + return false; + } + } + void FlipImage(gsl::span image, uint32_t height) { const size_t rowPitch{image.size() / height}; @@ -894,6 +934,13 @@ namespace Babylon StaticValue("ALPHA_INTERPOLATE", Napi::Number::From(env, AlphaMode::INTERPOLATE)), StaticValue("ALPHA_SCREENMODE", Napi::Number::From(env, AlphaMode::SCREENMODE)), + StaticValue("ALPHA_EQUATION_ADD", Napi::Number::From(env, AlphaEquation::ADD)), + StaticValue("ALPHA_EQUATION_SUBTRACT", Napi::Number::From(env, AlphaEquation::SUBTRACT)), + StaticValue("ALPHA_EQUATION_REVERSE_SUBTRACT", Napi::Number::From(env, AlphaEquation::REVERSE_SUBTRACT)), + StaticValue("ALPHA_EQUATION_MAX", Napi::Number::From(env, AlphaEquation::MAX)), + StaticValue("ALPHA_EQUATION_MIN", Napi::Number::From(env, AlphaEquation::MIN)), + StaticValue("ALPHA_EQUATION_DARKEN", Napi::Number::From(env, AlphaEquation::DARKEN)), + StaticValue("STENCIL_TEST_LESS", Napi::Number::From(env, BGFX_STENCIL_TEST_LESS)), StaticValue("STENCIL_TEST_LEQUAL", Napi::Number::From(env, BGFX_STENCIL_TEST_LEQUAL)), StaticValue("STENCIL_TEST_EQUAL", Napi::Number::From(env, BGFX_STENCIL_TEST_EQUAL)), @@ -962,6 +1009,7 @@ namespace Babylon StaticValue("COMMAND_SETDEPTHWRITE", Napi::FunctionPointer::Create(env, &NativeEngine::SetDepthWrite)), StaticValue("COMMAND_SETCOLORWRITE", Napi::FunctionPointer::Create(env, &NativeEngine::SetColorWrite)), StaticValue("COMMAND_SETBLENDMODE", Napi::FunctionPointer::Create(env, &NativeEngine::SetBlendMode)), + StaticValue("COMMAND_SETBLENDEQUATION", Napi::FunctionPointer::Create(env, &NativeEngine::SetBlendEquation)), StaticValue("COMMAND_SETFLOAT", Napi::FunctionPointer::Create(env, &NativeEngine::SetFloat)), StaticValue("COMMAND_SETFLOAT2", Napi::FunctionPointer::Create(env, &NativeEngine::SetFloat2)), StaticValue("COMMAND_SETFLOAT3", Napi::FunctionPointer::Create(env, &NativeEngine::SetFloat3)), @@ -1468,6 +1516,38 @@ namespace Babylon m_engineState |= blendMode; } + void NativeEngine::SetBlendEquation(NativeDataStream::Reader& data) + { + // OIT depth peeling requires MAX blending, not just blend factors. + const auto equation = data.ReadUint32(); + + m_engineState &= ~BGFX_STATE_BLEND_EQUATION_MASK; + switch (equation) + { + case AlphaEquation::ADD: + // BGFX_STATE_BLEND_EQUATION_ADD is 0 + break; + case AlphaEquation::SUBTRACT: + m_engineState |= BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_SUB); + break; + case AlphaEquation::REVERSE_SUBTRACT: + m_engineState |= BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_REVSUB); + break; + case AlphaEquation::MAX: + m_engineState |= BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_MAX); + break; + case AlphaEquation::MIN: + m_engineState |= BGFX_STATE_BLEND_EQUATION(BGFX_STATE_BLEND_EQUATION_MIN); + break; + case AlphaEquation::DARKEN: + // MIN for RGB, ADD for alpha — matches Constants.ALPHA_EQUATION_DARKEN. + m_engineState |= BGFX_STATE_BLEND_EQUATION_SEPARATE(BGFX_STATE_BLEND_EQUATION_MIN, BGFX_STATE_BLEND_EQUATION_ADD); + break; + default: + break; + } + } + void NativeEngine::SetInt(NativeDataStream::Reader& data) { const auto& uniformInfo{*data.ReadPointer()}; @@ -2591,12 +2671,18 @@ namespace Babylon const uint32_t samples = info[5].IsUndefined() ? 1 : info[5].As().Uint32Value(); const double layer = info[6].IsUndefined() ? 0 : info[6].As().DoubleValue(); const bool isCube = texture != nullptr && texture->IsCube(); - if (!std::isfinite(layer) || layer != std::floor(layer) || layer < 0 || layer > (isCube ? 5 : 0)) + const uint16_t maxLayer = texture == nullptr ? 0 : isCube ? 5 : + texture->Is3D() ? texture->Depth() - 1 : texture->NumLayers() - 1; + if (!std::isfinite(layer) || layer != std::floor(layer) || layer < 0 || layer > maxLayer) { throw Napi::RangeError::New(info.Env(), isCube - ? "Cube frame buffer face must be an integer between 0 and 5" - : "Non-cube frame buffer layer must be 0"); + ? "Cube frame buffer face must be an integer between 0 and 5" + : "Frame buffer layer must be an integer within the texture"); } + // Optional mip level for the color attachment (IBL voxel-grid mip-copy renders into a specific mip). + const uint16_t mip = (info.Length() > 7 && !info[7].IsUndefined()) ? static_cast(info[7].As().Uint32Value()) : 0; + // Disable automatic mip generation for targets whose mip levels are authored explicitly. + const bool autoGenerateMips = (info.Length() > 8 && !info[8].IsUndefined()) ? info[8].As().Value() : true; // A single render target is just the zero-or-one color attachment case of the shared implementation. const bool requestDepthStencilTexture = texture != nullptr && !texture->IsValid(); @@ -2607,8 +2693,8 @@ namespace Babylon Graphics::Texture* const colorTextures[]{texture}; const gsl::span colorAttachments{colorTextures, texture != nullptr && !requestDepthStencilTexture ? 1u : 0u}; - return CreateFrameBufferImpl(info.Env(), colorAttachments, width, height, generateStencilBuffer, generateDepth, samples, - static_cast(layer), requestDepthStencilTexture ? texture : nullptr); + return CreateFrameBufferImpl(info.Env(), colorAttachments, width, height, generateStencilBuffer, generateDepth, samples, static_cast(layer), mip, {}, + nullptr, autoGenerateMips, requestDepthStencilTexture ? texture : nullptr); } Napi::Value NativeEngine::CreateMultiFrameBuffer(const Napi::CallbackInfo& info) @@ -2633,10 +2719,31 @@ namespace Babylon colorTextures[i] = colorTexturesArray.Get(i).As>().Get(); } - return CreateFrameBufferImpl(info.Env(), gsl::span{colorTextures.data(), colorCount}, width, height, generateStencilBuffer, generateDepth, samples); + // Optional layer per color attachment, including distinct slices of a shared volume. + std::array perAttachmentLayers{}; + uint32_t layerCount = 0; + if (info.Length() > 6 && info[6].IsArray()) + { + const auto layersArray = info[6].As(); + const uint32_t rawLayerCount = layersArray.Length(); + layerCount = rawLayerCount < static_cast(perAttachmentLayers.size()) ? rawLayerCount : static_cast(perAttachmentLayers.size()); + for (uint32_t i = 0; i < layerCount; ++i) + { + perAttachmentLayers[i] = static_cast(layersArray.Get(i).As().Uint32Value()); + } + } + + // Optional shared depth texture for separately scheduled clear and geometry passes. + Graphics::Texture* explicitDepthTexture = nullptr; + if (info.Length() > 7 && !info[7].IsUndefined() && !info[7].IsNull()) + { + explicitDepthTexture = info[7].As>().Get(); + } + + return CreateFrameBufferImpl(info.Env(), gsl::span{colorTextures.data(), colorCount}, width, height, generateStencilBuffer, generateDepth, samples, 0, 0, gsl::span{perAttachmentLayers.data(), layerCount}, explicitDepthTexture); } - Napi::Value NativeEngine::CreateFrameBufferImpl(Napi::Env env, gsl::span colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, uint16_t layer, Graphics::Texture* depthStencilTexture) + Napi::Value NativeEngine::CreateFrameBufferImpl(Napi::Env env, gsl::span colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, uint16_t layer, uint16_t mip, gsl::span perAttachmentLayers, Graphics::Texture* explicitDepthTexture, bool autoGenerateMips, Graphics::Texture* depthStencilTexture) { const bgfx::Caps* caps = bgfx::getCaps(); const uint32_t colorCount = static_cast(colorTextures.size()); @@ -2653,60 +2760,113 @@ namespace Babylon std::array attachments{}; uint8_t numAttachments = 0; + // An uninitialized texture requests sampleable depth; alias the attachment back into it. + Graphics::Texture* depthStencilTextureRequest = depthStencilTexture; + + uint32_t colorIndex = 0; for (Graphics::Texture* texture : colorTextures) { - // bgfx validation now asserts when trying to use BGFX_ATTACHMENT_AUTO_GEN_MIPS with a texture that doesn't have the BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN flag, - // but before it would just ignore the flag and not generate mips without any warning. This prevents validation assert, but rendering might be broken if autogen - // mips were expected. Basically this change preserves previous behavior. - attachments[numAttachments++].init(texture->Handle(), bgfx::Access::Write, layer, 1, 0 - , 0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN) ? BGFX_ATTACHMENT_AUTO_GEN_MIPS : BGFX_ATTACHMENT_NONE + const uint16_t attachmentLayer = (colorIndex < perAttachmentLayers.size()) ? perAttachmentLayers[colorIndex] : layer; + ++colorIndex; + + if (texture == nullptr) + { + continue; + } + + if (!bgfx::isValid(texture->Handle())) + { + // Do not attach an uninitialized depth request as color. + depthStencilTextureRequest = texture; + continue; + } + + // Auto-generation requires format support and a base-mip attachment. + // Never regenerate explicitly authored chains: resolving mip 0 would overwrite higher levels. + const bool autoGenMips = (mip == 0) && autoGenerateMips && (0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN)); + attachments[numAttachments++].init(texture->Handle(), bgfx::Access::Write, attachmentLayer, 1, mip + , autoGenMips ? BGFX_ATTACHMENT_AUTO_GEN_MIPS : BGFX_ATTACHMENT_NONE ); } + // Create and alias shared depth once; subsequent framebuffers borrow its handle. + bool borrowedExplicitDepth = false; + // Match the depth-array slice to the color attachment's cascade layer. + const uint16_t depthAttachLayer = (perAttachmentLayers.size() > 0) ? perAttachmentLayers[0] : layer; + if (explicitDepthTexture != nullptr) + { + if (bgfx::isValid(explicitDepthTexture->Handle())) + { + const uint16_t attachLayer = + (explicitDepthTexture->NumLayers() > 1) ? depthAttachLayer : static_cast(0); + attachments[numAttachments++].init(explicitDepthTexture->Handle(), bgfx::Access::Write, attachLayer, 1, 0, BGFX_ATTACHMENT_NONE); + borrowedExplicitDepth = true; + } + else + { + depthStencilTextureRequest = explicitDepthTexture; + } + } + + const bool requestDepthStencilTexture = (depthStencilTextureRequest != nullptr); + 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) + uint64_t depthStencilTextureFlags = 0; + uint16_t depthStencilNumLayers = 1; + if ((generateStencilBuffer || generateDepth) && !borrowedExplicitDepth) { if (generateStencilBuffer && !generateDepth) { JsConsoleLogger::LogWarn(env, "Stencil without depth is not supported, assuming depth and stencil"); } - const auto rtFlag = RenderTargetSamplesToBgfxRtFlag(samples); - auto flags = BGFX_TEXTURE_RT_WRITE_ONLY | rtFlag; - if (depthStencilTexture != nullptr) + // Standalone depth is readable; ordinary depth attachments remain write-only. + // RT and MSAA levels share an exclusive flag field and must not be ORed together. + const uint64_t depthRtFlag = RenderTargetSamplesToBgfxRtFlag(samples); + auto flags = requestDepthStencilTexture ? depthRtFlag : (BGFX_TEXTURE_RT_WRITE_ONLY | depthRtFlag); + + // Readable MSAA depth must be sampled per-sample, not resolved. + if (requestDepthStencilTexture) { - // A standalone texture must be readable. Multisampled depth is sampled directly, not resolved. - flags = rtFlag | (rtFlag == BGFX_TEXTURE_RT ? BGFX_TEXTURE_NONE : BGFX_TEXTURE_MSAA_SAMPLE); + const uint64_t msaaBits = (flags & BGFX_TEXTURE_RT_MSAA_MASK) >> BGFX_TEXTURE_RT_MSAA_SHIFT; + if (msaaBits > 1) + { + flags |= BGFX_TEXTURE_MSAA_SAMPLE; + } + + // Match the depth-array size to the color array for cascaded shadows. + for (Graphics::Texture* colorTexture : colorTextures) + { + if (colorTexture != nullptr && bgfx::isValid(colorTexture->Handle()) && colorTexture->NumLayers() > depthStencilNumLayers) + { + depthStencilNumLayers = colorTexture->NumLayers(); + } + } } - // 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 - // newer bgfx asserts in createTexture2D when isTextureValid fails. Prefer D32F for - // depth-only when available; otherwise fall back to D24S8. Android always uses - // D24S8 — D32/D32F has produced glitches on several Mali devices - // (https://forum.babylonjs.com/t/post-processing-graphics-glitch/49523). + // Prefer a supported depth RT format. Android retains D24S8 for Mali compatibility: + // https://forum.babylonjs.com/t/post-processing-graphics-glitch/49523 bgfx::TextureFormat::Enum depthStencilFormat{bgfx::TextureFormat::D24S8}; #ifndef ANDROID if (!generateStencilBuffer) { - if (bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::D32F, flags)) + if (bgfx::isTextureValid(0, false, depthStencilNumLayers, bgfx::TextureFormat::D32F, flags)) { depthStencilFormat = bgfx::TextureFormat::D32F; } - else if (bgfx::isTextureValid(0, false, 1, bgfx::TextureFormat::D24, flags)) + else if (bgfx::isTextureValid(0, false, depthStencilNumLayers, bgfx::TextureFormat::D24, flags)) { depthStencilFormat = bgfx::TextureFormat::D24; } } #endif - if (!bgfx::isTextureValid(0, false, 1, depthStencilFormat, flags)) + if (!bgfx::isTextureValid(0, false, depthStencilNumLayers, depthStencilFormat, flags)) { throw Napi::Error::New(env, "No supported depth/stencil texture format for frame buffer"); } - depthStencilTextureHandle = bgfx::createTexture2D(width, height, false, 1, depthStencilFormat, flags); + depthStencilTextureHandle = bgfx::createTexture2D(width, height, false, depthStencilNumLayers, depthStencilFormat, flags); depthStencilTextureFormat = depthStencilFormat; depthStencilTextureFlags = flags; @@ -2715,7 +2875,8 @@ namespace Babylon // only allows mipmaps resolve step when mipmapping is asked and for the color texture, not the depth. // https://github.com/bkaradzic/bgfx/blob/2c21f68998595fa388e25cb6527e82254d0e9bff/src/renderer_d3d11.cpp#L4525 depthStencilAttachmentIndex = numAttachments; - attachments[numAttachments++].init(depthStencilTextureHandle, bgfx::Access::Write, 0, 1, 0, BGFX_ATTACHMENT_NONE); + const uint16_t attachLayer = (depthStencilNumLayers > 1) ? depthAttachLayer : static_cast(0); + attachments[numAttachments++].init(depthStencilTextureHandle, bgfx::Access::Write, attachLayer, 1, 0, BGFX_ATTACHMENT_NONE); } bgfx::FrameBufferHandle frameBufferHandle = bgfx::createFrameBuffer(numAttachments, attachments.data()); @@ -2726,16 +2887,35 @@ namespace Babylon bgfx::destroy(depthStencilTextureHandle); } - throw Napi::Error::New(env, "Failed to create frame buffer"); + // Report bgfx's specific framebuffer validation failure. + bx::Error fbErr; + bgfx::isFrameBufferValid(numAttachments, attachments.data(), &fbErr); + const bx::StringView reason = fbErr.getMessage(); + char message[512]; + snprintf(message, sizeof(message), "Failed to create frame buffer: %.*s", + static_cast(reason.getLength()), reason.getPtr()); + throw Napi::Error::New(env, message); } - const bool hasDepthAttachment = generateDepth || generateStencilBuffer; - Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, hasDepthAttachment, generateStencilBuffer, depthStencilAttachmentIndex); - if (depthStencilTexture != nullptr) + // Stencil-only requests also allocate depth; report it so clears and Z-writes remain enabled. + const bool hasDepthAttachment = generateDepth || generateStencilBuffer || borrowedExplicitDepth; + + // Shared depth is texture-owned; standalone aliases borrow framebuffer-owned depth. + int8_t frameBufferDepthOwnerIndex = depthStencilAttachmentIndex; + if (depthStencilTextureRequest != nullptr && depthStencilAttachmentIndex >= 0) { - // The framebuffer owns its depth attachment; expose a non-owning texture for sampling. - depthStencilTexture->Attach(depthStencilTextureHandle, width, height, false, 1, depthStencilTextureFormat, depthStencilTextureFlags); + const bool textureOwnsDepth = depthStencilTexture == nullptr; + depthStencilTextureRequest->Attach(bgfx::getTexture(frameBufferHandle, static_cast(depthStencilAttachmentIndex)), + textureOwnsDepth, width, height, false, depthStencilNumLayers, depthStencilTextureFormat, depthStencilTextureFlags); + if (textureOwnsDepth) + { + frameBufferDepthOwnerIndex = -1; + } } + + const bool isMultisampled = RenderTargetSamplesToBgfxRtFlag(samples) != BGFX_TEXTURE_RT; + Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, hasDepthAttachment, generateStencilBuffer, frameBufferDepthOwnerIndex, isMultisampled); + return Napi::Pointer::Create(env, frameBuffer, Napi::NapiPointerDeleter(frameBuffer)); } @@ -2850,7 +3030,6 @@ namespace Babylon void NativeEngine::Clear(NativeDataStream::Reader& data) { uint16_t flags{0}; - uint32_t rgba{0x000000ff}; const bool shouldClearColor{static_cast(data.ReadUint32())}; const float r{data.ReadFloat32()}; @@ -2864,14 +3043,9 @@ namespace Babylon bgfx::Encoder* encoder = GetEncoder(); + // Preserve out-of-range OIT clear colors without RGBA8 quantization. if (shouldClearColor) { - rgba = - (static_cast(r * std::numeric_limits::max()) << 24) + - (static_cast(g * std::numeric_limits::max()) << 16) + - (static_cast(b * std::numeric_limits::max()) << 8) + - static_cast(a * std::numeric_limits::max()); - flags |= BGFX_CLEAR_COLOR; } @@ -2885,7 +3059,7 @@ namespace Babylon flags |= BGFX_CLEAR_STENCIL; } - GetBoundFrameBuffer().Clear(*encoder, flags, rgba, depth, stencil); + GetBoundFrameBuffer().Clear(*encoder, flags, r, g, b, a, depth, stencil); } Napi::Value NativeEngine::GetRenderWidth(const Napi::CallbackInfo& info) @@ -3083,7 +3257,7 @@ namespace Babylon const uint32_t func{data.ReadUint32()}; const uint32_t ref{data.ReadUint32()}; - m_stencilState = BGFX_STENCIL_FUNC_RMASK(0xFF); // always 0xFF + m_stencilState = BGFX_STENCIL_FUNC_RMASK(0xFF); m_stencilState |= stencilOpFail; m_stencilState |= depthOpFail; // bgfx write mask is always 0xFF, to not change stencil value when writemask is 0 diff --git a/Plugins/NativeEngine/Source/NativeEngine.h b/Plugins/NativeEngine/Source/NativeEngine.h index 478e085d4..214f82ee9 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.h +++ b/Plugins/NativeEngine/Source/NativeEngine.h @@ -83,6 +83,7 @@ namespace Babylon void SetDepthWrite(NativeDataStream::Reader& data); void SetColorWrite(NativeDataStream::Reader& data); void SetBlendMode(NativeDataStream::Reader& data); + void SetBlendEquation(NativeDataStream::Reader& data); void SetMatrix(NativeDataStream::Reader& data); void SetInt(NativeDataStream::Reader& data); void SetIntArray(NativeDataStream::Reader& data); @@ -123,7 +124,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 colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, uint16_t layer = 0, Graphics::Texture* depthStencilTexture = nullptr); + Napi::Value CreateFrameBufferImpl(Napi::Env env, gsl::span colorTextures, uint16_t width, uint16_t height, bool generateStencilBuffer, bool generateDepth, uint32_t samples, uint16_t layer = 0, uint16_t mip = 0, gsl::span perAttachmentLayers = {}, Graphics::Texture* explicitDepthTexture = nullptr, bool autoGenerateMips = true, Graphics::Texture* depthStencilTexture = nullptr); void DeleteFrameBuffer(NativeDataStream::Reader& data); void BindFrameBuffer(NativeDataStream::Reader& data); void UnbindFrameBuffer(NativeDataStream::Reader& data); diff --git a/Plugins/NativeXr/Source/NativeXrImpl.cpp b/Plugins/NativeXr/Source/NativeXrImpl.cpp index f0f93ed34..8a17207c8 100644 --- a/Plugins/NativeXr/Source/NativeXrImpl.cpp +++ b/Plugins/NativeXr/Source/NativeXrImpl.cpp @@ -349,7 +349,7 @@ namespace Babylon // bgfx::begin failing); skip the clear rather than dereference it. if (clearEncoder != nullptr) { - frameBuffer.Clear(*clearEncoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0, 1.0f, 0); + frameBuffer.Clear(*clearEncoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0.f, 0.f, 0.f, 0.f, 1.0f, 0); } viewConfig.FrameBuffers[eyeIdx] = frameBufferPtr; diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index c0d4a1d4e..9c346cd9b 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -249,7 +249,7 @@ namespace Babylon::Polyfills::Internal m_texture = std::make_unique(m_graphicsContext); } - m_texture->Attach(bgfx::getTexture(m_frameBuffer->Handle()), m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + m_texture->Attach(bgfx::getTexture(m_frameBuffer->Handle()), /*ownsHandle*/ false, m_width, m_height, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); // Hand the blit view id reserved during the preceding Context::Flush to the texture so // NativeEngine::CopyTexture blits on a view ordered before the consuming layer. m_texture->BlitViewId(m_blitViewId, m_blitViewIdGeneration); diff --git a/Polyfills/Canvas/Source/Context.cpp b/Polyfills/Canvas/Source/Context.cpp index 484791e54..707c3a858 100644 --- a/Polyfills/Canvas/Source/Context.cpp +++ b/Polyfills/Canvas/Source/Context.cpp @@ -942,7 +942,7 @@ namespace Babylon::Polyfills::Internal frameBuffer.Bind(); if (needClear) { - frameBuffer.Clear(*encoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0, 1.f, 0); + frameBuffer.Clear(*encoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0.f, 0.f, 0.f, 0.f, 1.f, 0); } frameBuffer.SetViewPort(0.f, 0.f, 1.f, 1.f); const auto width = m_canvas->GetWidth(); @@ -960,7 +960,7 @@ namespace Babylon::Polyfills::Internal }; std::function release = [this, encoder](Babylon::Graphics::FrameBuffer* frameBuffer) -> void { // clear framebuffer when released - frameBuffer->Clear(*encoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0, 1.f, 0); + frameBuffer->Clear(*encoder, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH | BGFX_CLEAR_STENCIL, 0.f, 0.f, 0.f, 0.f, 1.f, 0); this->m_canvas->m_frameBufferPool.Release(frameBuffer); frameBuffer->Unbind(); }; From 453eec6380e0ce446a25cec3b2841bd8c2c3a33a Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:31 -0700 Subject: [PATCH 12/16] Extend texture formats, array layers, and volume rendering Extend render-target allocation to texture arrays and 3D volumes, map depth comparison functions, and use compatible backing storage for additional formats. Validate dimensions, layers, and comparison enums before mutation or integer narrowing, while preserving fractional-size truncation and default-layer behavior. Add allocation and invalid-input regressions. Review-Group: H2b Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 9429dd4eb0b6d0c790971dd5c2b1abcf48facaad Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 25 ++ .../Tests.NativeEngine.CubeRenderTargets.cpp | 124 ++++++++ .../Tests.NativeEngine.TextureFormats.cpp | 220 ++++++++++++++ .../Tests.ShaderCompilation.FragCoord.cpp | 268 +++++++++++++++++- .../Source/Tests.ShaderCompilation.cpp | 30 ++ .../Babylon/Graphics/FrameBuffer.h | 3 +- Core/Graphics/Source/FrameBuffer.cpp | 21 +- Core/Graphics/Source/Texture.cpp | 35 ++- Plugins/NativeEngine/Source/NativeEngine.cpp | 141 ++++++--- Plugins/NativeEngine/Source/NativeEngine.h | 1 + .../Source/ShaderCompilerCommon.cpp | 12 +- .../Source/ShaderCompilerDXBC.cpp | 3 +- .../Source/ShaderCompilerDXIL.cpp | 2 +- .../Source/ShaderCompilerMetal.cpp | 2 +- .../Source/ShaderCompilerTraversers.cpp | 118 +++----- .../Source/ShaderCompilerTraversers.h | 9 +- .../Source/ShaderCompilerVulkan.cpp | 2 +- 17 files changed, 876 insertions(+), 140 deletions(-) diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index 259ea1ba9..7c5d19acc 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -2128,6 +2128,31 @@ function createSceneAndWait(callback: (engine: NativeEngine, scene: Scene) => vo }); } +describe("NativeTextureFormats", function () { + it("creates a D24 render target with compatible backing storage", function () { + const engine = new _native.Engine(); + const texture = engine.createTexture(); + try { + engine.initializeTexture(texture, 16, 16, false, _native.Engine.TEXTURE_FORMAT_D24, true, false, 1); + expect(engine.getTextureWidth(texture)).to.equal(16); + expect(engine.getTextureHeight(texture)).to.equal(16); + } finally { + engine.deleteTexture(texture); + engine.dispose(); + } + }); + + it("rejects an invalid format without entering bgfx texture creation", function () { + const engine = new _native.Engine(); + const texture = engine.createTexture(); + try { + expect(() => engine.initializeTexture(texture, 16, 16, false, 0xffffffff, true, false, 1)).to.throw("Invalid texture format"); + } finally { + engine.deleteTexture(texture); + engine.dispose(); + } + }); +}); describe("Materials", function () { this.timeout(0); diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp index 370df975b..cb7200435 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.CubeRenderTargets.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,129 @@ extern Babylon::Graphics::Configuration g_deviceConfig; +TEST(NativeEngineCubeRenderTargets, GeneratedImageMipsPreserveRowsAndColumns) +{ + Babylon::Graphics::Device device{g_deviceConfig}; +#if defined(USE_NOOP_METAL_DEVICE) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP() << "GPU rendering/readback is unavailable in this test configuration"; +#endif + // An 8x8 PNG with red increasing across X and green increasing across Y. + constexpr std::array png{ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x08, 0x06, 0x00, 0x00, 0x00, 0xc4, 0x0f, 0xbe, + 0x8b, 0x00, 0x00, 0x00, 0x1d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0x60, 0x60, 0x60, 0xf8, + 0x8f, 0x8c, 0xff, 0xa3, 0x61, 0x06, 0x7a, 0x28, 0xf8, 0x8f, 0x0a, 0xff, 0xa3, 0x61, 0x3a, 0x28, + 0x00, 0x00, 0x6b, 0xb0, 0x7f, 0x81, 0x52, 0xa1, 0x36, 0x78, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, + 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; + constexpr uint16_t readbackWidth = 6 * (8 + 4 + 2); + std::array pixels{}; + std::promise completed; + auto future = completed.get_future(); + device.StartRenderingCurrentFrame(); + Babylon::AppRuntime runtime{}; + runtime.Dispatch([&](Napi::Env env) { + try + { + device.AddToJavaScript(env); + Babylon::Plugins::NativeEngine::Initialize(env); + auto engine = env.Global().Get("_native").As().Get("Engine").As().New({}); + auto value = engine.Get("createTexture").As().Call(engine, {}); + env.Global().Set("_testEngine", engine); + env.Global().Set("_testCube", value); + auto bytes = Napi::Uint8Array::New(env, png.size()); + std::copy(png.begin(), png.end(), bytes.Data()); + auto faces = Napi::Array::New(env, 6); + for (uint32_t face = 0; face < 6; ++face) + { + faces.Set(face, bytes); + } + auto onLoaded = Napi::Function::New(env, [&](const Napi::CallbackInfo& info) { + try + { + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(info.Env()); + auto frameScope = context.AcquireFrameCompletionScope(); + auto* cube = info.Env().Global().Get("_testCube").As>().Get(); + auto readback = std::make_shared(context); + readback->Create2D(readbackWidth, 8, false, 1, bgfx::TextureFormat::RGBA8, + BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK); + uint16_t destinationX{}; + for (uint16_t face = 0; face < 6; ++face) + { + for (uint8_t mip = 0; mip < 3; ++mip) + { + const uint16_t size = 8 >> mip; + bgfx::TextureRegion source{}; + source.init(cube->Handle(), 0, 0, size, size); + source.z = face; + source.depth = 1; + source.mip = mip; + bgfx::TextureRegion destination{}; + destination.init(readback->Handle(), destinationX, 0, size, size); + context.GetActiveEncoder()->blit(context.AcquireNewViewId(), destination, source); + destinationX += size; + } + } + context.ReadTextureAsync(readback->Handle(), gsl::make_span(pixels)) + .then(arcana::inline_scheduler, arcana::cancellation::none(), [readback, &completed](arcana::expected result) { + readback->Dispose(); + if (result.has_error()) + { + completed.set_exception(result.error()); + } + else + { + completed.set_value(); + } + }); + } + catch (...) + { + completed.set_exception(std::current_exception()); + } + }); + auto onError = Napi::Function::New(env, [&](const Napi::CallbackInfo&) { + completed.set_exception(std::make_exception_ptr(std::runtime_error{"Cube image loading failed"})); + }); + engine.Get("loadCubeTexture").As().Call(engine, { + value, faces, Napi::Boolean::New(env, true), Napi::Boolean::New(env, false), + Napi::Boolean::New(env, false), onLoaded, onError}); + } + catch (...) + { + completed.set_exception(std::current_exception()); + } + }); + while (future.wait_for(std::chrono::milliseconds{16}) != std::future_status::ready) + { + device.FinishRenderingCurrentFrame(); + device.StartRenderingCurrentFrame(); + } + EXPECT_NO_THROW(future.get()); + size_t sourceX{}; + for (uint16_t face = 0; face < 6; ++face) + { + SCOPED_TRACE(face); + for (uint8_t mip = 0; mip < 3; ++mip) + { + SCOPED_TRACE(mip); + const size_t size = 8 >> mip; + for (size_t y = 0; y < size; ++y) + { + for (size_t x = 0; x < size; ++x) + { + const size_t offset = (y * readbackWidth + sourceX + x) * 4; + EXPECT_EQ(pixels[offset], x < size / 2 ? 0 : 255); + EXPECT_EQ(pixels[offset + 1], y < size / 2 ? 255 : 0); + EXPECT_EQ(pixels[offset + 2], 0); + EXPECT_EQ(pixels[offset + 3], 255); + } + } + sourceX += size; + } + } + device.FinishRenderingCurrentFrame(); +} + TEST(NativeEngineCubeRenderTargets, ClearsEachFaceIndependentlyAndPreserves2DDefaults) { Babylon::Graphics::Device device{g_deviceConfig}; diff --git a/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp b/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp index a1eb6226a..d82a91c75 100644 --- a/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp +++ b/Apps/UnitTests/Source/Tests.NativeEngine.TextureFormats.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -9,12 +11,15 @@ #include #include +#include #include #include #include #include #include +#include #include +#include extern Babylon::Graphics::Configuration g_deviceConfig; @@ -200,6 +205,221 @@ TEST(NativeEngineTextureFormats, RejectedInitializationPreservesExistingTexture) }); } +TEST(NativeEngineTextureFormats, RejectedDimensionsAndLayersPreserveExistingTexture) +{ + RunTextureTest([](Napi::Object engine, Napi::Value value) { + const auto env = engine.Env(); + InitializeTexture(engine, value, bgfx::TextureFormat::RGBA8, true); + auto* texture = value.As>().Get(); + const auto originalHandle = texture->Handle(); + for (const double invalid : {-1.0, 0.0, 0.5, 65536.0, 4294967296.0, + std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity()}) + { + for (const size_t component : {size_t{1}, size_t{2}, size_t{9}}) + { + if (component == 9 && invalid == 0) + { + continue; + } + for (const bool volume : {false, true}) + { + std::vector args{ + value, Napi::Number::New(env, 16), Napi::Number::New(env, 16), + Napi::Boolean::New(env, false), Napi::Number::New(env, bgfx::TextureFormat::RGBA8), + Napi::Boolean::New(env, true), Napi::Boolean::New(env, false), Napi::Number::New(env, 1), + Napi::Boolean::New(env, !volume), Napi::Number::New(env, 1), Napi::Boolean::New(env, volume)}; + args[component] = Napi::Number::New(env, invalid); + if (!volume && component == 1) + { + args[2] = args[1]; + } + EXPECT_THROW(engine.Get("initializeTexture").As().Call(engine, args), Napi::Error); + EXPECT_EQ(texture->Handle().idx, originalHandle.idx); + EXPECT_EQ(texture->Width(), 16u); + EXPECT_EQ(texture->Height(), 16u); + } + } + } + }); +} + +TEST(NativeEngineTextureFormats, TruncatesFractionalDimensionsBeforeAllocation) +{ + RunTextureTest([](Napi::Object engine, Napi::Value value) { + const auto env = engine.Env(); + auto* texture = value.As>().Get(); + for (const bool cube : {false, true}) + { + engine.Get("initializeTexture").As().Call(engine, { + value, Napi::Number::New(env, 16.9), Napi::Number::New(env, cube ? 16.9 : 12.4), + Napi::Boolean::New(env, false), Napi::Number::New(env, bgfx::TextureFormat::RGBA8), + Napi::Boolean::New(env, true), Napi::Boolean::New(env, false), + Napi::Number::New(env, 1), Napi::Boolean::New(env, cube), Napi::Number::New(env, 0)}); + EXPECT_EQ(texture->Width(), 16u); + EXPECT_EQ(texture->Height(), cube ? 16u : 12u); + } + }); +} + +TEST(NativeEngineTextureFormats, DepthOneVolumesPreserveLogicalBoundsAndPadding) +{ + Babylon::Graphics::Device device{g_deviceConfig}; +#if defined(USE_NOOP_METAL_DEVICE) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP() << "GPU rendering/readback is unavailable in this test configuration"; +#endif + device.StartRenderingCurrentFrame(); + if (!bgfx::isTextureValid(2, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT | BGFX_TEXTURE_BLIT_DST)) + { + GTEST_SKIP() << "Volume render targets are unavailable"; + } + constexpr uint16_t width = 12; + std::array pixels{}; + std::promise completed; + auto future = completed.get_future(); + Babylon::AppRuntime runtime{}; + runtime.Dispatch([&](Napi::Env env) { + try + { + device.AddToJavaScript(env); + Babylon::Plugins::NativeEngine::Initialize(env); + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + auto scope = context.AcquireFrameCompletionScope(); + const auto engine = env.Global().Get("_native").As().Get("Engine").As().New({}); + env.Global().Set("_volumeTestEngine", engine); + auto textures = Napi::Array::New(env, 3); + env.Global().Set("_volumeTestTextures", textures); + auto readback = std::make_shared(context); + readback->Create2D(width, 2, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_READ_BACK); + for (uint32_t test = 0; test < 3; ++test) + { + const auto value = engine.Get("createTexture").As().Call(engine, {}); + textures.Set(test, value); + auto* texture = value.As>().Get(); + engine.Get("initializeTexture").As().Call(engine, { + value, Napi::Number::New(env, 2), Napi::Number::New(env, 2), + Napi::Boolean::New(env, false), Napi::Number::New(env, bgfx::TextureFormat::RGBA8), + Napi::Boolean::New(env, test == 2), Napi::Boolean::New(env, false), Napi::Number::New(env, 1), + Napi::Boolean::New(env, false), Napi::Number::New(env, 1), Napi::Boolean::New(env, true)}); + EXPECT_EQ(texture->Depth(), 1); + const auto createFrameBuffer = engine.Get("createFrameBuffer").As(); + std::vector frameBufferArgs{ + value, Napi::Number::New(env, 2), Napi::Number::New(env, 2), + Napi::Boolean::New(env, false), Napi::Boolean::New(env, false), + Napi::Number::New(env, 1), Napi::Number::New(env, 1)}; + EXPECT_THROW(createFrameBuffer.Call(engine, frameBufferArgs), Napi::Error); + frameBufferArgs.back() = Napi::Number::New(env, 0); + if (test == 1) + { +#ifdef HAS_NATIVE_IMAGE_LOADING + auto data = Napi::Uint8Array::New(env, 16); + std::fill_n(data.Data(), data.ElementLength(), uint8_t{255}); + engine.Get("loadRawTexture3D").As().Call(engine, { + value, data, Napi::Number::New(env, 2), Napi::Number::New(env, 2), Napi::Number::New(env, 1), + Napi::Number::New(env, bgfx::TextureFormat::RGBA8), Napi::Boolean::New(env, false), Napi::Boolean::New(env, false)}); + EXPECT_EQ(texture->Depth(), 1); + frameBufferArgs.back() = Napi::Number::New(env, 1); + EXPECT_THROW(createFrameBuffer.Call(engine, frameBufferArgs), Napi::Error); +#else + std::array data{}; + data.fill(255); + texture->Update3D(0, 0, 0, 0, 2, 2, 1, bgfx::copy(data.data(), uint32_t(data.size()))); +#endif + const std::array partial{17, 33, 65, 255}; + texture->Update3D(0, 1, 1, 0, 1, 1, 1, bgfx::copy(partial.data(), uint32_t(partial.size()))); + } + else if (test == 2) + { + const auto frameBufferValue = createFrameBuffer.Call(engine, frameBufferArgs); + auto* frameBuffer = frameBufferValue.As>().Get(); + const auto bind = Napi::Eval(env, R"( + (function(engine, frameBuffer) { + const stream = new _native.NativeDataStream(function() {}); + engine.setCommandDataStream({_nativeDataStream: stream}); + return function(bind) { + const command = bind ? _native.Engine.COMMAND_BINDFRAMEBUFFER : _native.Engine.COMMAND_UNBINDFRAMEBUFFER; + const words = new Uint32Array(command.length + frameBuffer.length); + words.set(command); + words.set(frameBuffer, command.length); + stream.writeBuffer(words.buffer, words.length); + engine.submitCommands(); + }; + }) + )", "depth-one-volume-bind.js").As().Call({engine, frameBufferValue}).As(); + bind.Call({Napi::Boolean::New(env, true)}); + frameBuffer->Clear(*context.GetActiveEncoder(), BGFX_CLEAR_COLOR, 1.f, 0.f, 1.f, 1.f, 1.f, 0); + bind.Call({Napi::Boolean::New(env, false)}); + } + for (uint16_t slice = 0; slice < 2; ++slice) + { + bgfx::TextureRegion source{}; + source.init(texture->Handle(), 0, 0, 2, 2); + source.z = slice; + source.depth = 1; + bgfx::TextureRegion destination{}; + destination.init(readback->Handle(), uint16_t(test * 4 + slice * 2), 0, 2, 2); + context.GetActiveEncoder()->blit(context.AcquireNewViewId(), destination, source); + } + } + context.ReadTextureAsync(readback->Handle(), gsl::make_span(pixels)) + .then(arcana::inline_scheduler, arcana::cancellation::none(), [readback, &completed](arcana::expected result) { + if (result.has_error()) + { + completed.set_exception(result.error()); + } + else + { + completed.set_value(); + } + }); + } + catch (...) + { + completed.set_exception(std::current_exception()); + } + }); + while (future.wait_for(std::chrono::milliseconds{16}) != std::future_status::ready) + { + device.FinishRenderingCurrentFrame(); + device.StartRenderingCurrentFrame(); + } + EXPECT_NO_THROW(future.get()); + for (uint16_t y = 0; y < 2; ++y) + { + for (uint16_t x = 0; x < width; ++x) + { + const auto test = x / 4; + const std::array expected = test == 0 ? std::array{} : + test == 2 ? std::array{255, 0, 255, 255} : + (x % 2 == 1 && y == 1) ? std::array{17, 33, 65, 255} : + std::array{255, 255, 255, 255}; + for (size_t channel = 0; channel < 4; ++channel) + { + EXPECT_EQ(pixels[(y * width + x) * 4 + channel], expected[channel]) << x << ", " << y << ", " << channel; + } + } + } + device.FinishRenderingCurrentFrame(); +} + +TEST(NativeEngineTextureFormats, RejectsComparisonEnumsWithoutChangingSampler) +{ + RunTextureTest([](Napi::Object engine, Napi::Value value) { + const auto env = engine.Env(); + auto* texture = value.As>().Get(); + constexpr uint32_t flags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_COMPARE_LESS; + texture->SamplerFlags(flags); + auto setComparison = engine.Get("setTextureComparisonFunction").As(); + for (const double invalid : {-1.0, 1.0, 520.0, 0x0201 + 0.5, 4294967296.0, + std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity()}) + { + EXPECT_THROW(setComparison.Call(engine, {value, Napi::Number::New(env, invalid)}), Napi::Error); + EXPECT_EQ(texture->SamplerFlags(), flags); + } + EXPECT_NO_THROW(setComparison.Call(engine, {value, Napi::Number::New(env, 0)})); + EXPECT_EQ(texture->SamplerFlags(), BGFX_SAMPLER_U_CLAMP); + }); +} + TEST(NativeEngineTextureFormats, SamplingModesSeparateLodClampFromFilterFlags) { RunTextureTest([](Napi::Object engine, Napi::Value value) { diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp index d104e00ec..bcee0cb8b 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.FragCoord.cpp @@ -3,18 +3,23 @@ #include #include +#include +#include #include #include #include #include #include +#include #include "Helpers.h" #include +#include #include #include #include +#include #include #include #include @@ -99,7 +104,8 @@ namespace const std::string& fragmentShader, bool withInputTexture, const std::string& setupScript = {}, - std::chrono::milliseconds renderTimeout = std::chrono::seconds{30}) + std::chrono::milliseconds renderTimeout = std::chrono::seconds{30}, + const std::function& nativeSetup = {}) { // Use clip-space geometry and bottom-left-origin UVs. Babylon::Graphics::Device device{g_deviceConfig}; @@ -146,9 +152,13 @@ namespace frameOpen = true; } }); - runtime.Dispatch([&device](Napi::Env env) { + runtime.Dispatch([&device, &nativeSetup](Napi::Env env) { env.Global().Set("globalThis", env.Global()); device.AddToJavaScript(env); + if (nativeSetup) + { + nativeSetup(env); + } Babylon::Polyfills::Console::Initialize(env, [](const char* message, auto) { std::cout << message << std::endl; @@ -373,13 +383,50 @@ namespace } }); - WaitForTestCompletion(renderFuture, renderTimeout, "quad preparation/render timed out"); + const auto deadline = std::chrono::steady_clock::now() + renderTimeout; + while (renderFuture.wait_for(std::chrono::milliseconds{16}) != std::future_status::ready) + { + if (std::chrono::steady_clock::now() >= deadline) + { + throw std::runtime_error{"quad preparation/render timed out"}; + } + device.FinishRenderingCurrentFrame(); + frameOpen = false; + device.StartRenderingCurrentFrame(); + frameOpen = true; + } + renderFuture.get(); device.FinishRenderingCurrentFrame(); frameOpen = false; return Helpers::ReadPixels(device.GetPlatformInfo(), outputTexture, width, height); } + + std::vector RenderVolumeQuad(const std::string& fragmentShader) + { + const auto initializeVolume = [](Napi::Env env) { + env.Global().Set("initializeTestVolume", Napi::Function::New(env, [](const Napi::CallbackInfo& info) { + auto& context = Babylon::Graphics::DeviceContext::GetFromJavaScript(info.Env()); + auto scope = context.AcquireFrameCompletionScope(); + auto* texture = info[0].As>().Get(); + texture->Create3D(2, 2, 2, false, bgfx::TextureFormat::RGBA8, BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT); + const std::array pixels{ + 64, 128, 192, 255, 64, 128, 192, 255, 64, 128, 192, 255, 64, 128, 192, 255, + 64, 128, 192, 255, 64, 128, 192, 255, 64, 128, 192, 255, 64, 128, 192, 255}; + bgfx::updateTexture3D(texture->Handle(), 0, 0, 0, 0, 2, 2, 2, bgfx::copy(pixels.data(), static_cast(pixels.size()))); + })); + }; + return RenderFullScreenQuad(2, 2, + "attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); }", + fragmentShader, false, R"( + var raw = BABYLON.RawTexture.CreateRGBATexture(new Uint8Array([0, 0, 0, 255]), + 1, 1, scene, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE); + initializeTestVolume(raw.getInternalTexture()._hardwareTexture.underlyingResource); + raw.getInternalTexture().is3D = true; + material.setTexture("volume", raw); + )", std::chrono::seconds{30}, initializeVolume); + } } TEST(ShaderCompilation, FragCoordSetupAndPreparationFailuresPropagate) @@ -424,6 +471,221 @@ TEST(ShaderCompilation, FragCoordSetupAndPreparationFailuresPropagate) #endif } +TEST(ShaderCompilation, MultiRowMorphTextureMatchesVertexAttributes) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + const std::string vertexShader = R"( + attribute vec3 position; + #include + #include[0..maxSimultaneousMorphTargets] + void main() + { + vec3 positionUpdated = position; + #include + #include[0..maxSimultaneousMorphTargets] + gl_Position = vec4(positionUpdated, 1.0); + } + )"; + const std::string fragmentShader = + "precision highp float;\n" + "void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }\n"; + std::vector expected; + for (bool useTexture : {false, true}) + { + const std::string setup = "var useTexture = " + std::string{useTexture ? "true" : "false"} + R"(; + var caps = engine.getCaps(); + var maxTextureSize = caps.maxTextureSize; + caps.maxTextureSize = 2; + try { + var manager = new BABYLON.MorphTargetManager(scene); + manager.useTextureToStoreTargets = useTexture; + manager.areUpdatesFrozen = true; + var first = new BABYLON.MorphTarget("first", 0.25, scene); + first.setPositions(new Float32Array([ + -0.75, -0.875, 0, 0.875, -0.75, 0, + 0.5, 0.75, 0, -0.5, 0.875, 0 + ])); + var second = new BABYLON.MorphTarget("second", 0.5, scene); + second.setPositions(new Float32Array([ + -0.875, -0.5, 0, 0.5, -0.875, 0, + 0.875, 0.5, 0, -0.75, 0.75, 0 + ])); + manager.addTarget(first); + manager.addTarget(second); + manager.areUpdatesFrozen = false; + quad.morphTargetManager = manager; + if (useTexture && (!manager.isUsingTextureForTargets || + manager._textureWidth !== 2 || manager._textureHeight !== 2)) { + throw new Error("Expected a two-layer, multi-row morph texture"); + } + } finally { + caps.maxTextureSize = maxTextureSize; + } + )"; + auto pixels = RenderFullScreenQuad(32, 32, vertexShader, fragmentShader, false, setup); + if (useTexture) + { + EXPECT_EQ(pixels, expected); + } + else + { + expected = std::move(pixels); + ASSERT_EQ(expected.size(), 32u * 32u * 4u); + EXPECT_EQ(expected[0], 0); + EXPECT_EQ(expected[(16 * 32 + 16) * 4], 255); + } + } +#endif +} + +TEST(ShaderCompilation, VolumeCoordinateSideEffectsExecuteOnce) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + for (const std::string coordinate : {"nextCoordinate()", "targetSize.x > 0.0 ? nextCoordinate() : vec3(0.0)"}) + { + SCOPED_TRACE(coordinate); + const std::string fragmentShader = R"( + precision highp float; + uniform highp sampler3D volume; + uniform vec2 targetSize; + int calls = 0; + vec3 nextCoordinate() { calls++; return vec3(0.25, 0.75, 0.25); } + void main() + { + vec4 sampled = texture(volume, )" + coordinate + R"(); + gl_FragColor = vec4(float(calls) * 0.25, sampled.g, sampled.b, 1.0); + } + )"; + const auto pixels = RenderVolumeQuad(fragmentShader); + ASSERT_EQ(pixels.size(), 16u); + for (size_t i = 0; i < pixels.size(); i += 4) + { + EXPECT_NEAR(pixels[i], 64, 1); + EXPECT_EQ(pixels[i + 1], 128); + EXPECT_EQ(pixels[i + 2], 192); + EXPECT_EQ(pixels[i + 3], 255); + } + } +#endif +} + +TEST(ShaderCompilation, IntegerVolumeCoordinatesExecuteOnce) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + for (const std::string coordinate : {"nextCoordinate()", "targetSize.x > 0.0 ? nextCoordinate() : ivec3(0)"}) + { + SCOPED_TRACE(coordinate); + const auto pixels = RenderVolumeQuad(R"( + precision highp float; + precision highp int; + uniform highp sampler3D volume; + uniform vec2 targetSize; + int calls = 0; + int lodCalls = 0; + ivec3 nextCoordinate() { calls++; return ivec3(0, 1, 0); } + int nextLod() { lodCalls++; return 0; } + void main() + { + vec4 sampled = texelFetch(volume, )" + coordinate + R"(, nextLod()); + gl_FragColor = vec4(float(calls) * 0.25, float(lodCalls) * 0.25, sampled.b, 1.0); + } + )"); + ASSERT_EQ(pixels.size(), 16u); + for (size_t i = 0; i < pixels.size(); i += 4) + { + EXPECT_NEAR(pixels[i], 64, 1); + EXPECT_NEAR(pixels[i + 1], 64, 1); + EXPECT_EQ(pixels[i + 2], 192); + EXPECT_EQ(pixels[i + 3], 255); + } + } +#endif +} + +TEST(ShaderCompilation, RawVolumeExplicitGradientsPreserveRows) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) || !defined(HAS_NATIVE_IMAGE_LOADING) + GTEST_SKIP(); +#else + const auto pixels = RenderFullScreenQuad(2, 2, + "attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); }", + R"( + precision highp float; + uniform highp sampler3D volume; + void main() + { + gl_FragColor = textureGrad(volume, vec3(0.25, 0.25, 0.75), + vec3(0.01, 0.03, 0.0), vec3(0.02, 0.04, 0.0)); + } + )", false, R"( + var raw = BABYLON.RawTexture.CreateRGBATexture(new Uint8Array([0, 0, 0, 255]), + 1, 1, scene, false, false, BABYLON.Texture.NEAREST_SAMPLINGMODE); + var data = new Uint8Array([ + 64, 32, 16, 255, 128, 32, 16, 255, 64, 224, 16, 255, 128, 224, 16, 255, + 64, 32, 192, 255, 128, 32, 192, 255, 64, 224, 192, 255, 128, 224, 192, 255]); + engine._engine.loadRawTexture3D(raw.getInternalTexture()._hardwareTexture.underlyingResource, + data, 2, 2, 2, testVolumeFormat, false, false); + if (data[1] !== 32 || data[9] !== 224) throw new Error("Volume upload mutated source rows"); + raw.getInternalTexture().is3D = true; + material.setTexture("volume", raw); + )", std::chrono::seconds{30}, [](Napi::Env env) { + env.Global().Set("testVolumeFormat", Napi::Number::New(env, bgfx::TextureFormat::RGBA8)); + }); + ASSERT_EQ(pixels.size(), 16u); + for (size_t i = 0; i < pixels.size(); i += 4) + { + EXPECT_EQ(pixels[i], 64); + EXPECT_EQ(pixels[i + 1], 32); + EXPECT_EQ(pixels[i + 2], 192); + EXPECT_EQ(pixels[i + 3], 255); + } +#endif +} + +TEST(NativeEngineReadback, FloatTexturesUsePinnedJavaScriptByteContract) +{ +#if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + const auto pixels = RenderFullScreenQuad(1, 1, + "attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); }", + "precision highp float; void main() { gl_FragColor = vec4(1.0); }", false, R"( + globalThis.__prepare = function () { + var texture = BABYLON.RawTexture.CreateRGBATexture( + new Float32Array([0.25, 0.5, 0.75, 1.0]), 1, 1, scene, false, false, + BABYLON.Texture.NEAREST_SAMPLINGMODE, BABYLON.Constants.TEXTURETYPE_FLOAT); + return texture.readPixels().then(function (bytes) { + if (!(bytes instanceof Uint8Array) || bytes.length !== 4) { + throw new Error("Default float readback must contain four RGBA8 bytes"); + } + [64, 128, 191, 255].forEach(function (value, index) { + if (Math.abs(bytes[index] - value) > 1) { + throw new Error("Incorrect float-to-byte readback at channel " + index); + } + }); + var destination = new Uint8Array(12); + destination.fill(91); + var region = destination.subarray(4, 8); + return texture.readPixels(0, 0, region).then(function (result) { + if (result !== region) throw new Error("Readback replaced the supplied buffer"); + for (var i = 0; i < destination.length; ++i) { + var expected = i >= 4 && i < 8 ? bytes[i - 4] : 91; + if (destination[i] !== expected) throw new Error("Readback changed bytes outside its view"); + } + }); + }); + }; + )"); + EXPECT_EQ(pixels, (std::vector{255, 255, 255, 255})); +#endif +} + TEST(NativeEngineViewport, MatchesClipSpaceRegion) { #if defined(SKIP_EXTERNAL_TEXTURE_TESTS) || defined(SKIP_RENDER_TESTS) diff --git a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp index 316bee087..fbba1e3ac 100644 --- a/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp +++ b/Apps/UnitTests/Source/Tests.ShaderCompilation.cpp @@ -139,6 +139,36 @@ TEST(ShaderCompilation, NativeCompilerAcceptsExistingVec4UniformArray) EXPECT_FALSE(shader.VertexBytes.empty()); EXPECT_FALSE(shader.FragmentBytes.empty()); } + +TEST(ShaderCompilation, SamplerCoordinatesAcceptConditionalExpressions) +{ + Babylon::Plugins::ShaderCompiler compiler{}; + auto shader = compiler.Compile( + R"( + in vec2 position; + void main() { gl_Position = vec4(position, 0.0, 1.0); } + )", + R"( + precision highp float; + uniform highp sampler3D volume; + uniform highp sampler2DShadow shadow; + uniform highp sampler2DArrayShadow shadowArray; + uniform vec4 coordinate; + layout(location = 0) out vec4 fragColor; + void main() + { + vec3 alternate = coordinate.zyx; + fragColor = texture(volume, coordinate.x > 0.0 ? coordinate.xyz : alternate); + fragColor += textureLod(volume, coordinate.x > 0.0 ? alternate : coordinate.xyz, 0.0); + fragColor += textureGrad(volume, coordinate.x > 0.0 ? coordinate.xyz : alternate, vec3(0.01), vec3(0.02)); + fragColor += texelFetch(volume, coordinate.x > 0.0 ? ivec3(coordinate.xyz) : ivec3(0), coordinate.x > 0.0 ? 0 : 1); + fragColor += vec4(texture(shadow, coordinate.x > 0.0 ? alternate : coordinate.xyz)); + fragColor += vec4(texture(shadowArray, coordinate.x > 0.0 ? coordinate : coordinate.wzyx)); + } + )"); + EXPECT_FALSE(shader.VertexBytes.empty()); + EXPECT_FALSE(shader.FragmentBytes.empty()); +} #endif TEST(ShaderCompilation, CompileComprehensiveGLSL) diff --git a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h index 9bfdd68b2..3ffda4f92 100644 --- a/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h +++ b/Core/Graphics/InternalInclude/Babylon/Graphics/FrameBuffer.h @@ -20,7 +20,7 @@ namespace Babylon::Graphics class FrameBuffer final { public: - FrameBuffer(DeviceContext& context, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex = -1, bool isMultisampled = false); + FrameBuffer(DeviceContext& context, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex = -1, bool isMultisampled = false, uint8_t depthOneVolumeAttachmentMask = 0); ~FrameBuffer(); FrameBuffer(const FrameBuffer&) = delete; @@ -64,6 +64,7 @@ namespace Babylon::Graphics const bool m_hasDepth{}; const bool m_hasStencil{}; const bool m_isMultisampled{}; + const uint8_t m_depthOneVolumeAttachmentMask{}; std::optional m_viewId{}; diff --git a/Core/Graphics/Source/FrameBuffer.cpp b/Core/Graphics/Source/FrameBuffer.cpp index b67881900..a8a8e7d7f 100644 --- a/Core/Graphics/Source/FrameBuffer.cpp +++ b/Core/Graphics/Source/FrameBuffer.cpp @@ -43,7 +43,7 @@ namespace namespace Babylon::Graphics { - FrameBuffer::FrameBuffer(DeviceContext& deviceContext, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex, bool isMultisampled) + FrameBuffer::FrameBuffer(DeviceContext& deviceContext, bgfx::FrameBufferHandle handle, uint16_t width, uint16_t height, bool defaultBackBuffer, bool hasDepth, bool hasStencil, int8_t depthStencilAttachmentIndex, bool isMultisampled, uint8_t depthOneVolumeAttachmentMask) : m_deviceContext{deviceContext} , m_deviceID{deviceContext.GetDeviceId()} , m_handle{handle} @@ -55,6 +55,7 @@ namespace Babylon::Graphics , m_hasDepth{hasDepth} , m_hasStencil{hasStencil} , m_isMultisampled{isMultisampled} + , m_depthOneVolumeAttachmentMask{depthOneVolumeAttachmentMask} , m_disposed{false} , m_depthStencilAttachmentIndex{depthStencilAttachmentIndex} { @@ -123,6 +124,24 @@ namespace Babylon::Graphics void FrameBuffer::Unbind() { + if (m_depthOneVolumeAttachmentMask == 0 || !m_viewId.has_value()) + { + return; + } + const auto view = m_deviceContext.AcquireNewViewId(); + bgfx::resetView(view); + for (uint8_t attachment = 0; attachment < MaxColorAttachments; ++attachment) + { + if ((m_depthOneVolumeAttachmentMask & (1 << attachment)) != 0) + { + bgfx::TextureRegion source{}; + source.init(bgfx::getTexture(m_handle, attachment), 0, 0, Width(), Height()); + source.depth = 1; + auto destination = source; + destination.z = 1; + m_deviceContext.GetActiveEncoder()->blit(view, destination, source); + } + } } void FrameBuffer::Clear(bgfx::Encoder& encoder, uint16_t flags, float r, float g, float b, float a, float depth, uint8_t stencil, uint8_t colorAttachmentMask) diff --git a/Core/Graphics/Source/Texture.cpp b/Core/Graphics/Source/Texture.cpp index 5476c232f..2a842b143 100644 --- a/Core/Graphics/Source/Texture.cpp +++ b/Core/Graphics/Source/Texture.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace @@ -215,14 +216,15 @@ namespace Babylon::Graphics { Dispose(); - // bgfx creates depth-1 volumes as Texture2D, which cannot bind to sampler3D. - // Pad to two slices to obtain a 3D view, including for placeholder volumes. - if (depth < 2) + // bgfx needs two physical slices for a 3D view; retain the logical depth. + const uint16_t physicalDepth = std::max(2, depth); + auto createFlags = flags; + if (depth == 1 && (flags & BGFX_TEXTURE_RT_MASK) != 0) { - depth = 2; + createFlags |= BGFX_TEXTURE_BLIT_DST; } - m_handle = bgfx::createTexture3D(width, height, depth, hasMips, format, flags); + m_handle = bgfx::createTexture3D(width, height, physicalDepth, hasMips, format, createFlags); if (!bgfx::isValid(m_handle)) { throw std::runtime_error{"Failed to create 3D texture"}; @@ -230,11 +232,34 @@ namespace Babylon::Graphics m_ownsHandle = true; SetMetadata(width, height, depth, hasMips, false, true, 1, format, flags); + if (depth == 1) + { + bgfx::TextureInfo info{}; + bgfx::calcTextureSize(info, width, height, physicalDepth, false, hasMips, 1, format); + for (uint8_t mip = 0; mip < info.numMips; ++mip) + { + const auto mipWidth = static_cast(std::max(1, width >> mip)); + const auto mipHeight = static_cast(std::max(1, height >> mip)); + const auto mipDepth = static_cast(std::max(1, physicalDepth >> mip)); + bgfx::TextureInfo mipInfo{}; + bgfx::calcTextureSize(mipInfo, mipWidth, mipHeight, mipDepth, false, false, 1, format); + const auto memory = bgfx::alloc(mipInfo.storageSize); + std::memset(memory->data, 0, memory->size); + bgfx::updateTexture3D(m_handle, mip, 0, 0, 0, mipWidth, mipHeight, mipDepth, memory); + } + } } void Texture::Update3D(uint8_t mip, uint16_t x, uint16_t y, uint16_t z, uint16_t width, uint16_t height, uint16_t depth, const bgfx::Memory* mem) { + // Keep the padding identical, including partial updates, for normalized filtering. + const auto padding = m_depth == 1 && mip == 0 && z == 0 && depth == 1 + ? bgfx::copy(mem->data, mem->size) : nullptr; bgfx::updateTexture3D(m_handle, mip, x, y, z, width, height, depth, mem); + if (padding) + { + bgfx::updateTexture3D(m_handle, mip, x, y, 1, width, height, 1, padding); + } } void Texture::CreateCube(uint16_t size, bool hasMips, uint16_t numLayers, bgfx::TextureFormat::Enum format, uint64_t flags) diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 1a378d3dd..dc6e952f5 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -38,10 +38,13 @@ namespace bgfx #include #include #include +#include +#include #include #include #include #include +#include #ifdef BABYLON_NATIVE_NATIVEENGINE_TEST_HOOKS #include @@ -52,6 +55,21 @@ namespace Babylon { namespace { + uint32_t ReadUnsignedInteger(const Napi::Value& value, const char* name, uint32_t maximum, uint32_t minimum = 0, bool truncate = false) + { + double number = value.As().DoubleValue(); + if (truncate) + { + number = std::trunc(number); + } + if (!std::isfinite(number) || number < minimum || number > maximum || std::floor(number) != number) + { + throw Napi::RangeError::New(value.Env(), std::string{name} + " must be a finite integer between " + + std::to_string(minimum) + " and " + std::to_string(maximum)); + } + return static_cast(number); + } + namespace TextureSampling { constexpr uint32_t SAMPLER_MAG_POINT = BGFX_SAMPLER_MAG_POINT; @@ -109,28 +127,6 @@ namespace Babylon constexpr uint32_t DARKEN = 5; } - // Normalize floating-point readbacks to RGBA32F to preserve depth/HDR values. - constexpr bool IsFloatTextureFormat(bgfx::TextureFormat::Enum format) - { - switch (format) - { - case bgfx::TextureFormat::R16F: - case bgfx::TextureFormat::RG16F: - case bgfx::TextureFormat::RGBA16F: - case bgfx::TextureFormat::R32F: - case bgfx::TextureFormat::RG32F: - case bgfx::TextureFormat::RGBA32F: - case bgfx::TextureFormat::RGB9E5F: - case bgfx::TextureFormat::RG11B10F: - case bgfx::TextureFormat::D16F: - case bgfx::TextureFormat::D24F: - case bgfx::TextureFormat::D32F: - return true; - default: - return false; - } - } - void FlipImage(gsl::span image, uint32_t height) { const size_t rowPitch{image.size() / height}; @@ -492,7 +488,7 @@ namespace Babylon { if (bgfx::getCaps()->originBottomLeft) { - FlipImage({const_cast(imageMip.m_data), imageMip.m_size}, image->m_height); + FlipImage({const_cast(imageMip.m_data), imageMip.m_size}, imageMip.m_height); } bgfx::ReleaseFn releaseFn{}; @@ -1061,6 +1057,7 @@ namespace Babylon InstanceMethod("getTextureWidth", &NativeEngine::GetTextureWidth), InstanceMethod("getTextureHeight", &NativeEngine::GetTextureHeight), InstanceMethod("getTextureLayerCount", &NativeEngine::GetTextureLayerCount), + InstanceMethod("setTextureComparisonFunction", &NativeEngine::SetTextureComparisonFunction), InstanceMethod("deleteTexture", &NativeEngine::DeleteTexture), InstanceMethod("readTexture", &NativeEngine::ReadTexture), @@ -1726,8 +1723,10 @@ namespace Babylon const auto texture = info[0].As>().Get(); const auto widthValue = info[1].As(); const auto heightValue = info[2].As(); - const uint16_t width = static_cast(widthValue.Uint32Value()); - const uint16_t height = static_cast(heightValue.Uint32Value()); + const uint32_t maxDimension = std::min(UINT16_MAX, bgfx::getCaps()->limits.maxTextureSize); + // Render-target ratios can be fractional; preserve WebGL's integer-size conversion. + const uint16_t width = static_cast(ReadUnsignedInteger(widthValue, "Texture width", maxDimension, 1, /*truncate*/ true)); + const uint16_t height = static_cast(ReadUnsignedInteger(heightValue, "Texture height", maxDimension, 1, /*truncate*/ true)); const bool hasMips = info[3].As(); const double formatValue = info[4].As().DoubleValue(); if (!std::isfinite(formatValue) || formatValue < 0 || formatValue >= static_cast(bgfx::TextureFormat::Count) || @@ -1740,7 +1739,10 @@ namespace Babylon const bool renderTarget = info[5].As(); const bool srgb = info[6].As(); const uint32_t samples = info[7].IsUndefined() ? 1 : info[7].As().Uint32Value(); - const bool isCube = !info[8].IsUndefined() && info[8].As(); + const bool isCube = info.Length() > 8 && !info[8].IsUndefined() && info[8].As(); + // Optional array-layer count; also carries volume depth when is3D is set. + const uint16_t numLayers = (info.Length() > 9 && !info[9].IsUndefined()) ? static_cast(ReadUnsignedInteger(info[9], "Texture layer/depth count", UINT16_MAX)) : 1; + const bool is3D = info.Length() > 10 && !info[10].IsUndefined() && info[10].As(); auto flags = BGFX_TEXTURE_NONE; if (renderTarget) @@ -1758,13 +1760,17 @@ namespace Babylon flags |= BGFX_TEXTURE_SRGB; } - // Texture::Create2D also adds BLIT_DST for Babylon-owned textures. - const auto createFlags = isCube ? flags : flags | BGFX_TEXTURE_BLIT_DST; - if (!bgfx::isTextureValid(0, isCube, 1, format, createFlags)) + // Match Create3D's depth padding and Create2D's additional allocation flag. + const uint16_t textureDepth = is3D ? (numLayers > 0 ? numLayers : 1) : 0; + const uint16_t textureLayers = (isCube || is3D) ? 1 : (numLayers > 0 ? numLayers : 1); + const auto createFlags = (!isCube && !is3D) || (is3D && textureDepth == 1 && renderTarget) + ? flags | BGFX_TEXTURE_BLIT_DST : flags; + const uint16_t physicalDepth = is3D ? std::max(2, textureDepth) : 0; + if (!bgfx::isTextureValid(physicalDepth, isCube, textureLayers, format, createFlags)) { // Some backends support 24-bit depth only with packed stencil storage. if (renderTarget && format == bgfx::TextureFormat::D24 && - bgfx::isTextureValid(0, isCube, 1, bgfx::TextureFormat::D24S8, createFlags)) + bgfx::isTextureValid(physicalDepth, isCube, textureLayers, bgfx::TextureFormat::D24S8, createFlags)) { format = bgfx::TextureFormat::D24S8; } @@ -1783,11 +1789,16 @@ namespace Babylon { throw Napi::RangeError::New(info.Env(), "Cube texture width and height must be equal"); } + // Cube render target: width is the per-face size. texture->CreateCube(width, hasMips, 1, format, flags); } + else if (is3D) + { + texture->Create3D(width, height, textureDepth, hasMips, format, flags); + } else { - texture->Create2D(width, height, hasMips, 1, format, flags); + texture->Create2D(width, height, hasMips, textureLayers, format, flags); } } @@ -2400,6 +2411,51 @@ namespace Babylon texture.SamplerFlags(flags); } + // A zero comparison function disables depth comparison. + void NativeEngine::SetTextureComparisonFunction(const Napi::CallbackInfo& info) + { + auto& texture = *info[0].As>().Get(); + const uint32_t comparisonFunction = ReadUnsignedInteger(info[1], "Texture comparison function", UINT32_MAX); + + uint32_t flags = texture.SamplerFlags(); + flags &= ~BGFX_SAMPLER_COMPARE_MASK; + + // GL enum values used by Babylon.Constants (NEVER=0x0200 .. ALWAYS=0x0207). + switch (comparisonFunction) + { + case 0: + break; + case 0x0200: // NEVER + flags |= BGFX_SAMPLER_COMPARE_NEVER; + break; + case 0x0201: // LESS + flags |= BGFX_SAMPLER_COMPARE_LESS; + break; + case 0x0202: // EQUAL + flags |= BGFX_SAMPLER_COMPARE_EQUAL; + break; + case 0x0203: // LEQUAL + flags |= BGFX_SAMPLER_COMPARE_LEQUAL; + break; + case 0x0204: // GREATER + flags |= BGFX_SAMPLER_COMPARE_GREATER; + break; + case 0x0205: // NOTEQUAL + flags |= BGFX_SAMPLER_COMPARE_NOTEQUAL; + break; + case 0x0206: // GEQUAL + flags |= BGFX_SAMPLER_COMPARE_GEQUAL; + break; + case 0x0207: // ALWAYS + flags |= BGFX_SAMPLER_COMPARE_ALWAYS; + break; + default: + throw Napi::RangeError::New(info.Env(), "Texture comparison function must be 0 or a GL comparison enum between 0x0200 and 0x0207"); + } + + texture.SamplerFlags(flags); + } + void NativeEngine::SetTexture(NativeDataStream::Reader& data) { const UniformInfo* uniformInfo = data.ReadPointer(); @@ -2590,7 +2646,7 @@ namespace Babylon // specific cube-map face, blit to a temp 2D texture. bgfx::read addresses a whole mip of one // slice via TextureRegion::z, but a cropped sub-rect still needs the blit path. Cube-face // reads use srcZ = face index on the source region of that blit. - if (isCubeFace || x != 0 || y != 0 || width != mipWidth || height != mipHeight || (texture->Flags() & BGFX_TEXTURE_READ_BACK) == 0) + if (isCubeFace || x != 0 || blitY != 0 || width != mipWidth || height != mipHeight || (texture->Flags() & BGFX_TEXTURE_READ_BACK) == 0) { const bgfx::TextureHandle blitTextureHandle{bgfx::createTexture2D(width, height, /*hasMips*/ false, /*numLayers*/ 1, sourceTextureFormat, BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK)}; @@ -2622,7 +2678,7 @@ namespace Babylon std::vector convertedTextureBuffer(targetTextureInfo.storageSize); if (!bimg::imageConvert(&Graphics::DeviceContext::GetDefaultAllocator(), convertedTextureBuffer.data(), bimg::TextureFormat::Enum(targetTextureInfo.format), textureBuffer.data(), bimg::TextureFormat::Enum(sourceTextureInfo.format), sourceTextureInfo.width, sourceTextureInfo.height, /*depth*/ 1)) { - throw std::runtime_error{"Texture conversion to RGBA8 failed."}; + throw std::runtime_error{"Texture conversion for readback failed."}; } textureBuffer = convertedTextureBuffer; #endif @@ -2759,6 +2815,7 @@ namespace Babylon // BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS is 8, so 8 color + 1 depth fits in a fixed array. std::array attachments{}; uint8_t numAttachments = 0; + uint8_t depthOneVolumeAttachmentMask{}; // An uninitialized texture requests sampleable depth; alias the attachment back into it. Graphics::Texture* depthStencilTextureRequest = depthStencilTexture; @@ -2781,6 +2838,17 @@ namespace Babylon continue; } + const auto layers = texture->IsCube() ? 6 : texture->Is3D() + ? std::max(1, texture->Depth() >> mip) : texture->NumLayers(); + if (attachmentLayer >= layers) + { + throw Napi::RangeError::New(env, "Frame buffer layer must be an integer within the texture"); + } + if (texture->Is3D() && texture->Depth() == 1 && mip == 0) + { + depthOneVolumeAttachmentMask |= static_cast(1 << numAttachments); + } + // Auto-generation requires format support and a base-mip attachment. // Never regenerate explicitly authored chains: resolving mip 0 would overwrite higher levels. const bool autoGenMips = (mip == 0) && autoGenerateMips && (0 != (caps->formats[texture->Format()] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN)); @@ -2914,7 +2982,7 @@ namespace Babylon } const bool isMultisampled = RenderTargetSamplesToBgfxRtFlag(samples) != BGFX_TEXTURE_RT; - Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, hasDepthAttachment, generateStencilBuffer, frameBufferDepthOwnerIndex, isMultisampled); + Graphics::FrameBuffer* frameBuffer = new Graphics::FrameBuffer(m_deviceContext, frameBufferHandle, width, height, false, hasDepthAttachment, generateStencilBuffer, frameBufferDepthOwnerIndex, isMultisampled, depthOneVolumeAttachmentMask); return Napi::Pointer::Create(env, frameBuffer, Napi::NapiPointerDeleter(frameBuffer)); } @@ -2928,7 +2996,10 @@ namespace Babylon void NativeEngine::BindFrameBuffer(NativeDataStream::Reader& data) { Graphics::FrameBuffer* frameBuffer = data.ReadPointer(); - m_boundFrameBuffer->Unbind(); + if (m_boundFrameBuffer != nullptr) + { + m_boundFrameBuffer->Unbind(); + } m_boundFrameBuffer = frameBuffer; m_boundFrameBuffer->Bind(); m_boundFrameBufferNeedsRebinding.Set(false); diff --git a/Plugins/NativeEngine/Source/NativeEngine.h b/Plugins/NativeEngine/Source/NativeEngine.h index 214f82ee9..5532c18f8 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.h +++ b/Plugins/NativeEngine/Source/NativeEngine.h @@ -117,6 +117,7 @@ namespace Babylon void SetTextureSampling(NativeDataStream::Reader& data); void SetTextureWrapMode(NativeDataStream::Reader& data); void SetTextureAnisotropicLevel(NativeDataStream::Reader& data); + void SetTextureComparisonFunction(const Napi::CallbackInfo& info); void SetTexture(NativeDataStream::Reader& data); void UnsetTexture(NativeDataStream::Reader& data); void DiscardAllTextures(NativeDataStream::Reader& data); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp index 56738ec3c..ff84b5610 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp @@ -37,15 +37,9 @@ namespace Babylon::ShaderCompilerCommon std::string ProcessSamplerFlip(std::string_view source) { - // The vertical (V) flip for both float sample coordinates (texture()/textureLod()) and - // integer texel coordinates (texelFetch()) is now performed by the FlipSamplerCoordinates - // AST traverser, not by a preprocessor macro. The macro form - // #define texelFetch(tex, uv, lod) texelFetch((tex), ivec2(...), (lod)) - // forced every coordinate through ivec2(...), so it could not compile against sampler3D / - // sampler2DArray ('no matching overloaded function'). The AST traverser knows the sampler - // dimensionality and only flips 2-component coordinates, leaving 3D/array fetches intact. - // This function is retained as an identity passthrough so the backend call sites don't need - // to change. + // The AST traverser flips 2D/volume coordinates, including explicit gradients and + // integer fetches, while preserving raw array rows and cube directions. + // Retain this identity passthrough for the existing backend call sites. return std::string{source}; } diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp index 135a534a1..f026337c2 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp @@ -102,8 +102,7 @@ namespace Babylon::Plugins } ShaderCompilerTraversers::IdGenerator ids{}; - // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). - ShaderCompilerTraversers::FlipSamplerCoordinates(program); + ShaderCompilerTraversers::FlipSamplerCoordinates(program, ids); // Run before uniform collection so the target-size uniform is included. ShaderCompilerTraversers::FlipFragCoordY(program, ids); auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp index 9ad3ffcde..b63fce6d7 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp @@ -179,7 +179,7 @@ namespace Babylon::Plugins ShaderCompilerTraversers::IdGenerator ids{}; // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). - ShaderCompilerTraversers::FlipSamplerCoordinates(program); + ShaderCompilerTraversers::FlipSamplerCoordinates(program, ids); // Must precede the uniform struct move, which collects the uniform this declares. ShaderCompilerTraversers::FlipFragCoordY(program, ids); auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp index 4eed0aa31..ad1ddc3b6 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp @@ -108,7 +108,7 @@ namespace Babylon::Plugins ShaderCompilerTraversers::IdGenerator ids{}; // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). - ShaderCompilerTraversers::FlipSamplerCoordinates(program); + ShaderCompilerTraversers::FlipSamplerCoordinates(program, ids); // Must precede the uniform struct move, which collects the uniform this declares. ShaderCompilerTraversers::FlipFragCoordY(program, ids); auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp index 35a21dd5d..9e7476138 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp @@ -2142,14 +2142,14 @@ namespace Babylon::ShaderCompilerTraversers class FlipSamplerCoordinatesTraverser : public TIntermTraverser { public: - static void Traverse(TProgram& program) + static void Traverse(TProgram& program, IdGenerator& ids) { for (auto stage : {EShLangVertex, EShLangFragment, EShLangCompute}) { auto* intermediate{program.getIntermediate(stage)}; if (intermediate != nullptr) { - FlipSamplerCoordinatesTraverser traverser{intermediate}; + FlipSamplerCoordinatesTraverser traverser{intermediate, ids}; intermediate->getTreeRoot()->traverse(&traverser); } } @@ -2158,12 +2158,12 @@ namespace Babylon::ShaderCompilerTraversers protected: virtual bool visitAggregate(TVisit visit, TIntermAggregate* node) override { - if (visit == EvPreVisit && (node->getOp() == EOpTexture || node->getOp() == EOpTextureLod)) + if (visit == EvPreVisit && (node->getOp() == EOpTexture || node->getOp() == EOpTextureLod || node->getOp() == EOpTextureGrad)) { auto& sequence = node->getSequence(); if (sequence.size() >= 2) { - // Flip only V; preserve shadow depth, array layer, and volume depth. Leave cube directions unchanged. + // Raw array uploads keep their rows; leave array coordinates and cube directions unchanged. auto* sampler = sequence[0]->getAsTyped(); auto* coordinate = sequence[1]->getAsTyped(); if (sampler != nullptr && coordinate != nullptr && @@ -2173,12 +2173,14 @@ namespace Babylon::ShaderCompilerTraversers { const TSampler& samp = sampler->getType().getSampler(); const int vecSize = coordinate->getType().getVectorSize(); - // Esd2D covers both sampler2D and sampler2DArray (arrayed flag separate). - if ((samp.is2D() || samp.dim == Esd3D) && vecSize >= 2 && vecSize <= 4) + if (!samp.arrayed && (samp.is2D() || samp.dim == Esd3D) && vecSize >= 2 && vecSize <= 4) { - sequence[1] = (vecSize == 2) - ? FlipVerticalCoordinate(coordinate) - : FlipVerticalCoordinateKeepTail(coordinate); + sequence[1] = FlipVerticalCoordinate(coordinate); + if (node->getOp() == EOpTextureGrad) + { + sequence[2] = FlipVerticalCoordinate(sequence[2]->getAsTyped(), true); + sequence[3] = FlipVerticalCoordinate(sequence[3]->getAsTyped(), true); + } } } } @@ -2200,7 +2202,15 @@ namespace Babylon::ShaderCompilerTraversers const int vecSize = coordinate->getType().getVectorSize(); if (vecSize == 2 || (vecSize == 3 && sampler->getType().getSampler().dim == Esd3D)) { - sequence[1] = FlipVerticalTexelCoordinate(coordinate, sampler, lod); + const auto& loc = coordinate->getLoc(); + TIntermSymbol coordinateTemp{m_ids.Next(), "bnTexelCoordinate", TType{EbtInt, EvqTemporary, vecSize}}; + TIntermSymbol lodTemp{m_ids.Next(), "bnTexelLod", TType{EbtInt, EvqTemporary}}; + auto* captureCoordinate = m_intermediate->addAssign(EOpAssign, m_intermediate->addSymbol(coordinateTemp), coordinate, loc); + auto* captureLod = m_intermediate->addAssign(EOpAssign, m_intermediate->addSymbol(lodTemp), lod, loc); + auto* flipped = FlipVerticalTexelCoordinate(m_intermediate->addSymbol(coordinateTemp), sampler, m_intermediate->addSymbol(lodTemp)); + // Keep evaluation inside the original expression, including conditional branches. + sequence[1] = m_intermediate->addComma(captureCoordinate, m_intermediate->addComma(captureLod, flipped, loc), loc); + sequence[2] = m_intermediate->addSymbol(lodTemp); } } } @@ -2210,78 +2220,32 @@ namespace Babylon::ShaderCompilerTraversers } private: - // Post-visit is enabled so that texelFetch coordinates can be rewritten after their - // children have been traversed. The rewrite copies the coordinate subtree, and doing - // that on the way down would leave the copy unvisited while the original still got - // flipped, so a nested texture() call inside the coordinate would be flipped in one - // reference but not the other. - FlipSamplerCoordinatesTraverser(TIntermediate* intermediate) + // Rewrite texelFetch after nested samples have been visited. + FlipSamplerCoordinatesTraverser(TIntermediate* intermediate, IdGenerator& ids) : TIntermTraverser{true, false, true} , m_intermediate{intermediate} + , m_ids{ids} { } - // Builds `coordinate * vec2(1.0, -1.0) + vec2(0.0, 1.0)`, i.e. (u, 1.0 - v). - TIntermTyped* FlipVerticalCoordinate(TIntermTyped* coordinate) + // Component-wise math evaluates the coordinate once and preserves shadow/volume depth. + TIntermTyped* FlipVerticalCoordinate(TIntermTyped* coordinate, bool isGradient = false) { const TSourceLoc& loc{coordinate->getLoc()}; - TType vec2Type{EbtFloat, EvqConst, 2}; - - TConstUnionArray scaleValues{2}; - scaleValues[0].setDConst(1.0); - scaleValues[1].setDConst(-1.0); - TIntermTyped* scale{m_intermediate->addConstantUnion(scaleValues, vec2Type, loc)}; - - TConstUnionArray offsetValues{2}; - offsetValues[0].setDConst(0.0); - offsetValues[1].setDConst(1.0); - TIntermTyped* offset{m_intermediate->addConstantUnion(offsetValues, vec2Type, loc)}; - - TIntermTyped* scaled{m_intermediate->addBinaryMath(EOpMul, coordinate, scale, loc)}; - return m_intermediate->addBinaryMath(EOpAdd, scaled, offset, loc); - } - - // Preserve trailing components. Scalar extracts avoid OpVectorTimesScalar in SPIRV_CROSS_WEBMIN. - TIntermTyped* FlipVerticalCoordinateKeepTail(TIntermTyped* coordinate) - { - const TSourceLoc& loc{coordinate->getLoc()}; - const int vecSize = static_cast(coordinate->getType().getVectorSize()); - TType floatType{EbtFloat, EvqTemporary, 1}; - TType resultType{EbtFloat, EvqTemporary, vecSize}; - - // coordinate is read once per component; clone so no node ends up with two parents. - TIntermTyped* coordXSrc = coordinate; - TIntermTyped* coordYSrc = CloneExpression(coordinate); - TIntermTyped* coordZSrc = (vecSize >= 3) ? CloneExpression(coordinate) : nullptr; - TIntermTyped* coordWSrc = (vecSize >= 4) ? CloneExpression(coordinate) : nullptr; - - TIntermTyped* x{m_intermediate->addIndex(EOpIndexDirect, coordXSrc, m_intermediate->addConstantUnion(0, loc), loc)}; - x->setType(floatType); - TIntermTyped* y{m_intermediate->addIndex(EOpIndexDirect, coordYSrc, m_intermediate->addConstantUnion(1, loc), loc)}; - y->setType(floatType); - - TConstUnionArray oneValues{1}; - oneValues[0].setDConst(1.0); - TIntermTyped* one{m_intermediate->addConstantUnion(oneValues, TType{EbtFloat, EvqConst, 1}, loc)}; - TIntermTyped* flippedY{m_intermediate->addBinaryMath(EOpSub, one, y, loc)}; - - TIntermAggregate* ctor{m_intermediate->makeAggregate(x, loc)}; - ctor = m_intermediate->growAggregate(ctor, flippedY, loc); - if (vecSize >= 3) - { - TIntermTyped* z{m_intermediate->addIndex(EOpIndexDirect, coordZSrc, m_intermediate->addConstantUnion(2, loc), loc)}; - z->setType(floatType); - ctor = m_intermediate->growAggregate(ctor, z, loc); - } - if (vecSize >= 4) + const int vecSize = coordinate->getType().getVectorSize(); + TType vectorType{EbtFloat, EvqConst, vecSize}; + TConstUnionArray scaleValues{vecSize}; + TConstUnionArray offsetValues{vecSize}; + for (int i = 0; i < vecSize; ++i) { - TIntermTyped* w{m_intermediate->addIndex(EOpIndexDirect, coordWSrc, m_intermediate->addConstantUnion(3, loc), loc)}; - w->setType(floatType); - ctor = m_intermediate->growAggregate(ctor, w, loc); + scaleValues[i].setDConst(i == 1 ? -1.0 : 1.0); + offsetValues[i].setDConst(i == 1 && !isGradient ? 1.0 : 0.0); } + TIntermTyped* scale{m_intermediate->addConstantUnion(scaleValues, vectorType, loc)}; + TIntermTyped* offset{m_intermediate->addConstantUnion(offsetValues, vectorType, loc)}; - const TOperator op = (vecSize == 3) ? EOpConstructVec3 : EOpConstructVec4; - return m_intermediate->setAggregateOperator(ctor, op, resultType, loc); + TIntermTyped* scaled{m_intermediate->addBinaryMath(EOpMul, coordinate, scale, loc)}; + return m_intermediate->addBinaryMath(EOpAdd, scaled, offset, loc); } // Build (x, textureSize(sampler, lod).y - 1 - y[, z]). @@ -2290,6 +2254,7 @@ namespace Babylon::ShaderCompilerTraversers { const TSourceLoc& loc{coordinate->getLoc()}; + // The coordinate and LOD are captured symbols, not the original expressions. // Clone repeated operands so each AST node retains one parent. TIntermTyped* samplerClone{CloneExpression(sampler)}; TIntermTyped* lodClone{CloneExpression(lod)}; @@ -2337,9 +2302,7 @@ namespace Babylon::ShaderCompilerTraversers // verbatim rather than rebuilt through TIntermediate::add*, so no constant folding, // type promotion or precision inference can make the copy diverge from the original. // - // Anything not reachable in a texel coordinate expression (ternaries, array methods) - // is reported rather than silently skipped -- returning the coordinate unflipped would - // sample with an un-flipped Y and produce a wrong image with no diagnostic at all. + // Unsupported sampler expressions are reported rather than silently left unflipped. TIntermTyped* CloneExpression(TIntermTyped* node) { if (node == nullptr) @@ -2414,6 +2377,7 @@ namespace Babylon::ShaderCompilerTraversers } TIntermediate* m_intermediate{}; + IdGenerator& m_ids; }; /// Convert gl_FragCoord to GL coordinates on top-left-origin backends. @@ -2696,9 +2660,9 @@ namespace Babylon::ShaderCompilerTraversers InvertYDerivativeOperandsTraverser::Traverse(program); } - void FlipSamplerCoordinates(TProgram& program) + void FlipSamplerCoordinates(TProgram& program, IdGenerator& ids) { - FlipSamplerCoordinatesTraverser::Traverse(program); + FlipSamplerCoordinatesTraverser::Traverse(program, ids); } void FlipFragCoordY(TProgram& program, IdGenerator& ids) diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h index 049eb4829..cb0ac8010 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h @@ -169,10 +169,11 @@ namespace Babylon::ShaderCompilerTraversers /// https://github.com/bkaradzic/bgfx/blob/7be225bf490bb1cd231cfb4abf7e617bf35b59cb/src/bgfx_shader.sh#L62-L65 void InvertYDerivativeOperands(glslang::TProgram& program); - /// Flip only texture-coordinate Y on D3D/Metal/Vulkan, including shadow, array, and volume samplers. - /// For texelFetch, use the selected mip height. Preserve all other components and cube directions. - /// Do not apply on OpenGL. - void FlipSamplerCoordinates(glslang::TProgram& program); + /// Flip only texture-coordinate Y on D3D/Metal/Vulkan, including shadow and volume samplers. + /// For textureGrad, also reflect gradient Y without translation. For texelFetch, capture + /// coordinates and LOD once and use the selected mip height. Preserve other components and cube directions. + /// Leave raw array coordinates unchanged. Do not apply on OpenGL. + void FlipSamplerCoordinates(glslang::TProgram& program, IdGenerator& ids); /// Convert fragment coordinates with bnFragCoordTargetSize on D3D/Metal/Vulkan. /// Run before uniform collection; shaders without gl_FragCoord remain unchanged. diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp index c925f2a95..eace9900c 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp @@ -389,7 +389,7 @@ namespace Babylon::Plugins ShaderCompilerTraversers::IdGenerator ids{}; // Flip 2D texture sample coordinates (replaces the former ProcessSamplerFlip texture() macro). - ShaderCompilerTraversers::FlipSamplerCoordinates(program); + ShaderCompilerTraversers::FlipSamplerCoordinates(program, ids); // Must precede the uniform struct move, which collects the uniform this declares. ShaderCompilerTraversers::FlipFragCoordY(program, ids); auto cutScope = ShaderCompilerTraversers::ChangeUniformTypes(program, ids); From 93558128db62c04b92e234574b4e8df231831605 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:42 -0700 Subject: [PATCH 13/16] Expose Canvas alpha representations and dynamic mipmaps Keep Canvas render targets premultiplied internally, but produce a separate straight-alpha GPU copy when a dynamic texture requests it. Preserve the legacy premultiplied behavior when the argument is omitted and regenerate mipmaps for updated dynamic textures. Add conversion shaders for each backend and pixel-backed Canvas regressions; this contract requires matching Babylon.js uploads. Review-Group: H4 Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 29b5f90601b9055e21f50f979697ddc2b8a5d87a Split-Part: code --- .../JavaScript/src/tests.javaScript.all.ts | 2 + .../src/tests.nativeEngine.canvasImage.ts | 153 +++++++++++++++ .../Source/Tests.Canvas.Readback.cpp | 109 +++++++++++ Plugins/NativeEngine/Source/NativeEngine.cpp | 22 ++- Polyfills/Canvas/Source/Canvas.cpp | 183 ++++++++++++++++++ Polyfills/Canvas/Source/Canvas.h | 15 ++ .../Shaders/dxbc/fs_canvas_unpremultiply.h | 48 +++++ .../Shaders/dxil/fs_canvas_unpremultiply.h | 171 ++++++++++++++++ .../Shaders/essl/fs_canvas_unpremultiply.h | 66 +++++++ .../Source/Shaders/fs_canvas_unpremultiply.sc | 21 ++ .../Shaders/glsl/fs_canvas_unpremultiply.h | 59 ++++++ .../Shaders/metal/fs_canvas_unpremultiply.h | 82 ++++++++ .../Shaders/spirv/fs_canvas_unpremultiply.h | 116 +++++++++++ Polyfills/Canvas/shaderc.cmake | 2 +- 14 files changed, 1043 insertions(+), 6 deletions(-) create mode 100644 Apps/UnitTests/JavaScript/src/tests.nativeEngine.canvasImage.ts create mode 100644 Polyfills/Canvas/Source/Shaders/dxbc/fs_canvas_unpremultiply.h create mode 100644 Polyfills/Canvas/Source/Shaders/dxil/fs_canvas_unpremultiply.h create mode 100644 Polyfills/Canvas/Source/Shaders/essl/fs_canvas_unpremultiply.h create mode 100644 Polyfills/Canvas/Source/Shaders/fs_canvas_unpremultiply.sc create mode 100644 Polyfills/Canvas/Source/Shaders/glsl/fs_canvas_unpremultiply.h create mode 100644 Polyfills/Canvas/Source/Shaders/metal/fs_canvas_unpremultiply.h create mode 100644 Polyfills/Canvas/Source/Shaders/spirv/fs_canvas_unpremultiply.h diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index 7c5d19acc..cbe638a28 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -1,6 +1,7 @@ import * as Mocha from "mocha"; import { expect } from "chai"; import { registerPngTests } from "./tests.nativeEngine.png"; +import { registerCanvasImageTests } from "./tests.nativeEngine.canvasImage"; import { Buffer } from "buffer"; import { RequestFile, @@ -232,6 +233,7 @@ describe("Native texture readback", function () { } }); }); +registerCanvasImageTests(describe, it, skipCanvasGpuTests); describe("RequestFile", function () { this.timeout(0); diff --git a/Apps/UnitTests/JavaScript/src/tests.nativeEngine.canvasImage.ts b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.canvasImage.ts new file mode 100644 index 000000000..a0473d86c --- /dev/null +++ b/Apps/UnitTests/JavaScript/src/tests.nativeEngine.canvasImage.ts @@ -0,0 +1,153 @@ +import * as Mocha from "mocha"; +import { expect } from "chai"; +import { Constants, DynamicTexture, NativeEngine, Scene } from "@babylonjs/core"; + +type CanvasContext = ReturnType; + +const opaqueOrangePng = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAFUlEQVR4nGP838DwnwEJMCFziBMAAKTRAobDsKmdAAAAAElFTkSuQmCC"; + +function fillImageData(context: CanvasContext, width: number, height: number, pixel: (x: number, y: number) => readonly number[], dx = 0, dy = 0): void { + const imageData = context.getImageData(0, 0, width, height); + for (let y = 0; y < height; ++y) { + for (let x = 0; x < width; ++x) { + imageData.data.set(pixel(x, y), (y * width + x) * 4); + } + } + context.putImageData(imageData, dx, dy); +} + +async function readTexture(texture: DynamicTexture): Promise { + texture.update(false); + const pixels = await texture.readPixels(); + if (!(pixels instanceof Uint8Array)) { + throw new Error("Expected RGBA8 GPU readback for the canvas texture"); + } + // Native readback uses WebGL's bottom-up row order; assertions use Canvas coordinates. + const { width, height } = texture.getSize(); + const stride = width * 4; + const topDown = new Uint8Array(pixels.length); + for (let y = 0; y < height; ++y) { + topDown.set(pixels.subarray(y * stride, (y + 1) * stride), (height - 1 - y) * stride); + } + return topDown; +} + +export function registerCanvasImageTests( + describe: typeof Mocha.describe, + it: typeof Mocha.it, + skipCanvasGpuTests: boolean +): void { + describe("NativeEngine Canvas pixel-backed images", function () { + this.timeout(10000); + const test = skipCanvasGpuTests ? it.skip : it; + + test("keeps putImageData pixels alive through the GPU flush", async function () { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const texture = new DynamicTexture( + "putImageData lifetime", + { width: 10, height: 8 }, + scene, + false, + Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + fillImageData(texture.getContext(), 4, 3, () => [0, 255, 255, 255], 3, 2); + + const pixels = await readTexture(texture); + const pixel = (x: number, y: number) => Array.from(pixels.subarray((y * 10 + x) * 4, (y * 10 + x + 1) * 4)); + expect(pixel(4, 3), "inside putImageData rectangle").to.deep.equal([0, 255, 255, 255]); + expect(pixel(2, 3), "left of putImageData rectangle").to.deep.equal([0, 0, 0, 0]); + expect(pixel(4, 5), "below putImageData rectangle").to.deep.equal([0, 0, 0, 0]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + + test("draws an offscreen canvas at a nonzero destination", async function () { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const source = engine.createCanvas(4, 4); + fillImageData(source.getContext("2d"), 4, 4, () => [255, 0, 255, 255]); + + const texture = new DynamicTexture( + "offscreen canvas copy", + { width: 12, height: 10 }, + scene, + false, + Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(source, 3, 2); + + const pixels = await readTexture(texture); + const pixel = (x: number, y: number) => Array.from(pixels.subarray((y * 12 + x) * 4, (y * 12 + x + 1) * 4)); + expect(pixel(4, 3), "inside copied canvas").to.deep.equal([255, 0, 255, 255]); + expect(pixel(2, 3), "left of copied canvas").to.deep.equal([0, 0, 0, 0]); + expect(pixel(4, 6), "below copied canvas").to.deep.equal([0, 0, 0, 0]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + + test("draws an ImageBitmap source through the GPU path", async function () { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const bitmap = await engine._createImageBitmapFromSource(opaqueOrangePng); + expect(bitmap.width).to.equal(4); + expect(bitmap.height).to.equal(4); + + const texture = new DynamicTexture( + "ImageBitmap copy", + { width: 10, height: 8 }, + scene, + false, + Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(bitmap, 3, 2); + + const pixels = await readTexture(texture); + const pixel = (x: number, y: number) => Array.from(pixels.subarray((y * 10 + x) * 4, (y * 10 + x + 1) * 4)); + expect(pixel(4, 3), "inside copied ImageBitmap").to.deep.equal([255, 128, 0, 255]); + expect(pixel(2, 3), "left of copied ImageBitmap").to.deep.equal([0, 0, 0, 0]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + + test("crops and scales a nonzero offscreen-canvas source rectangle", async function () { + const engine = new NativeEngine(); + const scene = new Scene(engine); + try { + const source = engine.createCanvas(18, 8); + fillImageData(source.getContext("2d"), 18, 8, (x) => ( + x < 6 ? [255, 0, 0, 255] : + x < 12 ? [0, 255, 0, 255] : + [0, 0, 255, 255] + )); + + const texture = new DynamicTexture( + "offscreen canvas crop", + { width: 14, height: 12 }, + scene, + false, + Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(source, 7, 2, 4, 4, 3, 2, 8, 8); + + const pixels = await readTexture(texture); + const pixel = (x: number, y: number) => Array.from(pixels.subarray((y * 14 + x) * 4, (y * 14 + x + 1) * 4)); + expect(pixel(4, 5), "left interior of scaled crop").to.deep.equal([0, 255, 0, 255]); + expect(pixel(9, 5), "right interior of scaled crop").to.deep.equal([0, 255, 0, 255]); + expect(pixel(2, 5), "outside destination rectangle").to.deep.equal([0, 0, 0, 0]); + } finally { + scene.dispose(); + engine.dispose(); + } + }); + }); +} diff --git a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp index 0ef61921e..d5d68d31c 100644 --- a/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp +++ b/Apps/UnitTests/Source/Tests.Canvas.Readback.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -15,6 +17,7 @@ #include "../../../Polyfills/Canvas/Source/Path2D.h" #include "../../../Polyfills/Canvas/Source/nanovg/nanovg.h" #include "../../../Polyfills/Canvas/Source/nanovg/nanovg_filterstack.h" +#include #include #include @@ -354,6 +357,112 @@ TEST(CanvasReadback, UntouchedCanvasReadbackCreatesRenderTarget) }); } +TEST(CanvasReadback, CanvasTexturePreservesLegacyPremultipliedSource) +{ + RunCanvasTest([](Napi::Env env) { + const auto constructor = Babylon::JsRuntime::NativeObject::GetFromJavaScript(env).Get("Canvas").As(); + auto canvas = constructor.New({}); + canvas.Set("width", 4); + canvas.Set("height", 4); + auto context = canvas.Get("getContext").As().Call(canvas, {Napi::String::New(env, "2d")}).As(); + auto getTexture = canvas.Get("getCanvasTexture").As(); + EXPECT_THROW(getTexture.Call(canvas, {}), Napi::Error); + context.Get("flush").As().Call(context, {}); + auto* nativeCanvas = Babylon::Polyfills::Internal::NativeCanvas::Unwrap(canvas); + const auto source = bgfx::getTexture(nativeCanvas->GetFrameBuffer().Handle()); + for (const std::vector& arguments : std::vector>{ + {}, {env.Undefined()}, {Napi::Boolean::New(env, true)}}) + { + auto* texture = getTexture.Call(canvas, arguments).As>().Get(); + EXPECT_EQ(texture->Handle().idx, source.idx); + } + auto* straight = getTexture.Call(canvas, {Napi::Boolean::New(env, false)}).As>().Get(); + EXPECT_NE(straight->Handle().idx, source.idx); + EXPECT_EQ(straight->Width(), 4u); + EXPECT_EQ(straight->Height(), 4u); + EXPECT_EQ(bgfx::getTexture(nativeCanvas->GetFrameBuffer().Handle()).idx, source.idx); + }); +} + +TEST(CanvasReadback, QueuedRepresentationsPreservePixelsAndMips) +{ +#if defined(USE_NOOP_METAL_DEVICE) || defined(SKIP_RENDER_TESTS) + GTEST_SKIP(); +#else + RunCanvasTest([](Napi::Env env) { + auto canvas = Babylon::JsRuntime::NativeObject::GetFromJavaScript(env).Get("Canvas").As().New({}); + canvas.Set("width", 4); + canvas.Set("height", 4); + auto context = canvas.Get("getContext").As().Call(canvas, {Napi::String::New(env, "2d")}).As(); + context.Set("fillStyle", "rgba(200, 100, 50, 0.5)"); + context.Get("fillRect").As().Call(context, { + Napi::Number::New(env, 0), Napi::Number::New(env, 0), Napi::Number::New(env, 4), Napi::Number::New(env, 4)}); + context.Get("flush").As().Call(context, {}); + auto& graphics = Babylon::Graphics::DeviceContext::GetFromJavaScript(env); + auto pixels = std::make_shared>(); + auto completed = std::make_shared>(); + auto future = completed->get_future(); + { + auto frameScope = graphics.AcquireFrameCompletionScope(); + const auto getTexture = canvas.Get("getCanvasTexture").As(); + std::array textures{}; + for (size_t i = 0; i < textures.size(); ++i) + { + textures[i] = getTexture.Call(canvas, {Napi::Boolean::New(env, i == 2), Napi::Boolean::New(env, i != 0)}) + .As>().Get(); + } + auto readback = std::make_shared(graphics); + readback->Create2D(16, 4, false, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_BLIT_DST | BGFX_TEXTURE_READ_BACK); + for (uint16_t i = 0; i < 5; ++i) + { + const uint8_t mip = i < 3 ? 0 : 1; + const uint16_t size = 4 >> mip; + const uint16_t x = i < 3 ? i * 4 : 12 + (i - 3) * 2; + bgfx::TextureRegion source{}; + source.init(textures[i < 3 ? i : i - 2]->Handle(), 0, 0, size, size); + source.mip = mip; + bgfx::TextureRegion destination{}; + destination.init(readback->Handle(), x, 0, size, size); + graphics.GetActiveEncoder()->blit(graphics.AcquireNewViewId(), destination, source); + } + graphics.ReadTextureAsync(readback->Handle(), gsl::make_span(*pixels)) + .then(arcana::inline_scheduler, arcana::cancellation::none(), + [readback, pixels, completed](arcana::expected result) { + readback->Dispose(); + if (result.has_error()) + { + completed->set_exception(result.error()); + } + else + { + completed->set_value(); + } + }); + } + ASSERT_EQ(future.wait_for(std::chrono::seconds{30}), std::future_status::ready); + ASSERT_NO_THROW(future.get()); + for (size_t i = 0; i < 5; ++i) + { + SCOPED_TRACE(i); + const size_t size = i < 3 ? 4 : 2; + const size_t offsetX = i < 3 ? i * 4 : 12 + (i - 3) * 2; + const bool premultiplied = i == 2 || i == 4; + for (size_t y = 0; y < size; ++y) + { + for (size_t x = 0; x < size; ++x) + { + const size_t offset = (y * 16 + offsetX + x) * 4; + EXPECT_NEAR((*pixels)[offset], premultiplied ? 100 : 200, 2); + EXPECT_NEAR((*pixels)[offset + 1], premultiplied ? 50 : 100, 2); + EXPECT_NEAR((*pixels)[offset + 2], premultiplied ? 25 : 50, 2); + EXPECT_NEAR((*pixels)[offset + 3], 128, 1); + } + } + } + }); +#endif +} + TEST(CanvasReadback, EncodesPngWithoutInputImageLoading) { RunCanvasTest([](Napi::Env env) { diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index dc6e952f5..5dc06d1ee 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -1875,11 +1875,23 @@ namespace Babylon { blitView = m_deviceContext.PeekNextViewId(); } - bgfx::TextureRegion dstRegion{}; - dstRegion.init(textureDestination->Handle()); - bgfx::TextureRegion srcRegion{}; - srcRegion.init(textureSource->Handle()); - encoder->blit(blitView, dstRegion, srcRegion); + bgfx::TextureInfo sourceInfo{}, destinationInfo{}; + bgfx::calcTextureSize(sourceInfo, textureSource->Width(), textureSource->Height(), + std::max(1, textureSource->Depth()), textureSource->IsCube(), + textureSource->HasMips(), textureSource->NumLayers(), textureSource->Format()); + bgfx::calcTextureSize(destinationInfo, textureDestination->Width(), textureDestination->Height(), + std::max(1, textureDestination->Depth()), textureDestination->IsCube(), + textureDestination->HasMips(), textureDestination->NumLayers(), textureDestination->Format()); + for (uint8_t mip = 0; mip < std::min(sourceInfo.numMips, destinationInfo.numMips); ++mip) + { + bgfx::TextureRegion dstRegion{}; + dstRegion.init(textureDestination->Handle()); + dstRegion.mip = mip; + bgfx::TextureRegion srcRegion{}; + srcRegion.init(textureSource->Handle()); + srcRegion.mip = mip; + encoder->blit(blitView, dstRegion, srcRegion); + } } void NativeEngine::LoadRawTexture(const Napi::CallbackInfo& info) diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index 9c346cd9b..8642354fe 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -2,8 +2,11 @@ #include "Image.h" #include "Path2D.h" #include "Context.h" +#include "NativeInstanceRegistry.h" #include +#include #include +#include #include #include #include @@ -17,9 +20,26 @@ #include #include +#include "Shaders/dxbc/vs_fspass.h" +#include "Shaders/dxil/vs_fspass.h" +#include "Shaders/metal/vs_fspass.h" +#include "Shaders/glsl/vs_fspass.h" +#include "Shaders/essl/vs_fspass.h" +#include "Shaders/spirv/vs_fspass.h" +#include "Shaders/dxbc/fs_canvas_unpremultiply.h" +#include "Shaders/dxil/fs_canvas_unpremultiply.h" +#include "Shaders/metal/fs_canvas_unpremultiply.h" +#include "Shaders/glsl/fs_canvas_unpremultiply.h" +#include "Shaders/essl/fs_canvas_unpremultiply.h" +#include "Shaders/spirv/fs_canvas_unpremultiply.h" + namespace { constexpr auto JS_CANVAS_NAME = "_CanvasImpl"; + const bgfx::EmbeddedShader CanvasCopyShaders[]{ + BGFX_EMBEDDED_SHADER(vs_fspass), + BGFX_EMBEDDED_SHADER(fs_canvas_unpremultiply), + BGFX_EMBEDDED_SHADER_END()}; } namespace Babylon::Polyfills::Internal @@ -231,6 +251,11 @@ namespace Babylon::Polyfills::Internal { m_texture.reset(); } + for (auto& converted : m_convertedTextures) + { + converted.Texture.reset(); + converted.FrameBuffer.reset(); + } m_frameBufferPool.Clear(); m_frameBufferPool.SetDimensions(m_width, m_height); @@ -244,6 +269,25 @@ namespace Babylon::Polyfills::Internal Napi::Value NativeCanvas::GetCanvasTexture(const Napi::CallbackInfo& info) { + if (!m_frameBuffer) + { + throw Napi::Error::New(info.Env(), "Canvas.getCanvasTexture requires a flushed context."); + } + // Omitted arguments preserve the raw, premultiplied texture used by older engines. + const bool premulAlpha = info.Length() == 0 || info[0].IsUndefined() || info[0].As().Value(); + const bool generateMipMaps = info.Length() > 1 && !info[1].IsUndefined() && info[1].As().Value(); + if (!premulAlpha || generateMipMaps) + { + try + { + return Napi::Pointer::Create(info.Env(), &GetConvertedTexture(premulAlpha, generateMipMaps)); + } + catch (const std::exception& ex) + { + throw Napi::Error::New(info.Env(), ex.what()); + } + } + if (!m_texture) { m_texture = std::make_unique(m_graphicsContext); @@ -256,6 +300,125 @@ namespace Babylon::Polyfills::Internal return Napi::Pointer::Create(info.Env(), m_texture.get()); } + Graphics::Texture& NativeCanvas::GetConvertedTexture(bool premulAlpha, bool generateMipMaps) + { + auto scope = m_graphicsContext.AcquireFrameCompletionScope(); + m_graphicsContext.FlushViewsIfNeeded(); + auto* encoder = m_graphicsContext.GetActiveEncoder(); + if (encoder == nullptr) + { + throw std::runtime_error{"Canvas alpha conversion requires an active graphics encoder."}; + } + + if (!bgfx::isValid(m_unpremultiplyProgram)) + { + const auto renderer = bgfx::getRendererType(); + const auto vertex = bgfx::createEmbeddedShader(CanvasCopyShaders, renderer, "vs_fspass"); + const auto fragment = bgfx::createEmbeddedShader(CanvasCopyShaders, renderer, "fs_canvas_unpremultiply"); + if (!bgfx::isValid(vertex) || !bgfx::isValid(fragment)) + { + if (bgfx::isValid(vertex)) + { + bgfx::destroy(vertex); + } + if (bgfx::isValid(fragment)) + { + bgfx::destroy(fragment); + } + throw std::runtime_error{"Cannot create Canvas alpha conversion shaders."}; + } + m_unpremultiplyProgram = bgfx::createProgram(vertex, fragment, true); + if (!bgfx::isValid(m_unpremultiplyProgram)) + { + throw std::runtime_error{"Cannot link Canvas alpha conversion shaders."}; + } + } + if (!bgfx::isValid(m_copySampler)) + { + m_copySampler = bgfx::createUniform("s_tex", bgfx::UniformType::Sampler); + } + if (!bgfx::isValid(m_copyParams)) + { + m_copyParams = bgfx::createUniform("u_canvasCopy", bgfx::UniformType::Vec4); + } + if (!bgfx::isValid(m_copySampler) || !bgfx::isValid(m_copyParams)) + { + throw std::runtime_error{"Cannot allocate Canvas alpha conversion uniforms."}; + } + if (generateMipMaps && !(bgfx::getCaps()->formats[bgfx::TextureFormat::RGBA8] & BGFX_CAPS_FORMAT_TEXTURE_MIP_AUTOGEN)) + { + throw std::runtime_error{"Canvas mipmaps require RGBA8 automatic mip generation."}; + } + // Keep representations separate: queued copies retain these Texture pointers. + auto& converted = m_convertedTextures[generateMipMaps ? (premulAlpha ? 2 : 1) : 0]; + if (!converted.FrameBuffer) + { + const auto color = bgfx::createTexture2D(m_width, m_height, generateMipMaps, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + if (!bgfx::isValid(color)) + { + throw std::runtime_error{"Cannot allocate Canvas straight-alpha texture."}; + } + bgfx::Attachment attachment{}; + attachment.init(color, bgfx::Access::Write, 0, 1, 0, generateMipMaps ? BGFX_ATTACHMENT_AUTO_GEN_MIPS : BGFX_ATTACHMENT_NONE); + const auto frameBuffer = bgfx::createFrameBuffer(1, &attachment, true); + if (!bgfx::isValid(frameBuffer)) + { + bgfx::destroy(color); + throw std::runtime_error{"Cannot allocate Canvas straight-alpha framebuffer."}; + } + converted.FrameBuffer = std::make_unique( + m_graphicsContext, frameBuffer, m_width, m_height, false, false, false, -1, false); + } + if (!converted.Texture) + { + converted.Texture = std::make_unique(m_graphicsContext); + converted.Texture->Attach(bgfx::getTexture(converted.FrameBuffer->Handle()), false, + m_width, m_height, generateMipMaps, 1, bgfx::TextureFormat::RGBA8, BGFX_TEXTURE_RT); + } + + bgfx::VertexLayout layout{}; + layout.begin().add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float).end(); + if (bgfx::getAvailTransientVertexBuffer(3, layout) != 3) + { + throw std::runtime_error{"Cannot allocate Canvas alpha conversion vertices."}; + } + bgfx::TransientVertexBuffer buffer{}; + bgfx::allocTransientVertexBuffer(&buffer, 3, layout); + const std::array, 3> vertices{{ + {-1.f, 0.f, 0.f, 0.f, 0.f}, {1.f, 0.f, 0.f, 1.f, 0.f}, {1.f, 2.f, 0.f, 1.f, 1.f}}}; + std::memcpy(buffer.data, vertices.data(), sizeof(vertices)); + + // Use the canvas reservation for conversion, then reserve the following blit + // before NativeEngine records the scene that samples the dynamic texture. + const auto conversionView = !m_blitViewUsedForConversion && m_blitViewId != UINT16_MAX && m_blitViewIdGeneration == m_graphicsContext.ViewIdGeneration() + ? m_blitViewId : m_graphicsContext.AcquireNewViewId(); + m_blitViewUsedForConversion = true; + const auto blitView = m_graphicsContext.AcquireNewViewId(); + bgfx::resetView(conversionView); + bgfx::setViewFrameBuffer(conversionView, converted.FrameBuffer->Handle()); + bgfx::setViewRect(conversionView, 0, 0, m_width, m_height); + bgfx::setViewMode(conversionView, bgfx::ViewMode::Sequential); + const std::array parameters{bgfx::getCaps()->originBottomLeft ? 1.f : 0.f, premulAlpha ? 1.f : 0.f, 0.f, 0.f}; + encoder->discard(BGFX_DISCARD_ALL); + encoder->setVertexBuffer(0, &buffer); + encoder->setTexture(0, m_copySampler, bgfx::getTexture(m_frameBuffer->Handle()), + BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_MIP_POINT); + encoder->setUniform(m_copyParams, parameters.data()); + encoder->setState(BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + encoder->submit(conversionView, m_unpremultiplyProgram); + if (generateMipMaps) + { + // A blit-only view need not switch render targets. Touch it so the + // conversion target resolves/generates its mip chain before the copy. + bgfx::resetView(blitView); + bgfx::setViewRect(blitView, 0, 0, m_width, m_height); + encoder->touch(blitView); + } + converted.Texture->BlitViewId(blitView, m_graphicsContext.ViewIdGeneration()); + return *converted.Texture; + } + Napi::Value NativeCanvas::ToDataURL(const Napi::CallbackInfo& info) { // PNG is also the Canvas fallback for empty or unsupported media types. @@ -303,6 +466,26 @@ namespace Babylon::Polyfills::Internal void NativeCanvas::Dispose() { + for (auto& converted : m_convertedTextures) + { + converted.Texture.reset(); + converted.FrameBuffer.reset(); + } + if (bgfx::isValid(m_unpremultiplyProgram)) + { + bgfx::destroy(m_unpremultiplyProgram); + m_unpremultiplyProgram = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_copySampler)) + { + bgfx::destroy(m_copySampler); + m_copySampler = BGFX_INVALID_HANDLE; + } + if (bgfx::isValid(m_copyParams)) + { + bgfx::destroy(m_copyParams); + m_copyParams = BGFX_INVALID_HANDLE; + } m_frameBuffer.reset(); m_texture.reset(); m_frameBufferPool.Clear(); diff --git a/Polyfills/Canvas/Source/Canvas.h b/Polyfills/Canvas/Source/Canvas.h index abe2cc6b1..707f079d4 100644 --- a/Polyfills/Canvas/Source/Canvas.h +++ b/Polyfills/Canvas/Source/Canvas.h @@ -7,6 +7,7 @@ #include #include "FrameBufferPool.h" +#include namespace Babylon::Polyfills { @@ -80,6 +81,7 @@ namespace Babylon::Polyfills::Internal uint32_t GetWidth() const { return m_width; } uint32_t GetHeight() const { return m_height; } + // Bound 2D context, if getContext("2d") has been called; nullptr otherwise. Context* GetBoundContext() const { return m_context; } void SetBoundContext(Context* context) { m_context = context; } @@ -95,6 +97,7 @@ namespace Babylon::Polyfills::Internal { m_blitViewId = viewId; m_blitViewIdGeneration = generation; + m_blitViewUsedForConversion = false; } Graphics::DeviceContext& GetGraphicsContext() @@ -109,9 +112,11 @@ namespace Babylon::Polyfills::Internal Napi::Value GetHeight(const Napi::CallbackInfo&); void SetHeight(const Napi::CallbackInfo&, const Napi::Value& value); Napi::Value GetCanvasTexture(const Napi::CallbackInfo& info); + Graphics::Texture& GetConvertedTexture(bool premulAlpha, bool generateMipMaps); Napi::Value ToDataURL(const Napi::CallbackInfo& info); static void LoadTTF(const Napi::CallbackInfo& info); static Napi::Value LoadTTFAsync(const Napi::CallbackInfo& info); + // Share validation while naming the entry point the caller used. static void LoadTTFCore(const Napi::CallbackInfo& info, const char* methodName); static Napi::Value ParseColor(const Napi::CallbackInfo& info); void Remove(const Napi::CallbackInfo& info); @@ -128,8 +133,18 @@ namespace Babylon::Polyfills::Internal std::unique_ptr m_frameBuffer; std::unique_ptr m_texture{}; + struct ConvertedTexture + { + std::unique_ptr FrameBuffer{}; + std::unique_ptr Texture{}; + }; + std::array m_convertedTextures{}; + bgfx::ProgramHandle m_unpremultiplyProgram{BGFX_INVALID_HANDLE}; + bgfx::UniformHandle m_copySampler{BGFX_INVALID_HANDLE}; + bgfx::UniformHandle m_copyParams{BGFX_INVALID_HANDLE}; bgfx::ViewId m_blitViewId{UINT16_MAX}; uint32_t m_blitViewIdGeneration{0}; + bool m_blitViewUsedForConversion{}; bool m_dirty{}; bool m_clear{}; diff --git a/Polyfills/Canvas/Source/Shaders/dxbc/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/dxbc/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..82bccc540 --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/dxbc/fs_canvas_unpremultiply.h @@ -0,0 +1,48 @@ +static const uint8_t fs_canvas_unpremultiply_dxbc[713] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, // .......u_canvasC + 0x6f, 0x70, 0x79, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, // opy...........s_ + 0x74, 0x65, 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x84, 0x02, 0x00, // tex0............ + 0x00, 0x44, 0x58, 0x42, 0x43, 0x49, 0x42, 0x6f, 0x01, 0x79, 0x3e, 0xe7, 0xd9, 0x0e, 0x88, 0xeb, // .DXBCIBo.y>..... + 0x80, 0x56, 0xb5, 0x1b, 0xc8, 0x01, 0x00, 0x00, 0x00, 0x84, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, // .V.............. + 0x00, 0x2c, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, // .,...........ISG + 0x4e, 0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, // Nh...........P.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x49, 0x4f, 0x4e, // .....SV_POSITION + 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0xab, 0xab, 0xab, 0x4f, 0x53, 0x47, // .TEXCOORD....OSG + 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, // N,........... .. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x41, 0x52, 0x47, 0x45, 0x54, 0x00, 0xab, // .....SV_TARGET.. + 0xab, 0x53, 0x48, 0x45, 0x58, 0xac, 0x01, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, // .SHEX....P...k.. + 0x00, 0x6a, 0x08, 0x00, 0x01, 0x59, 0x00, 0x00, 0x04, 0x46, 0x8e, 0x20, 0x00, 0x00, 0x00, 0x00, // .j...Y...F. .... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x03, 0x00, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, // .....Z....`..... + 0x00, 0x58, 0x18, 0x00, 0x04, 0x00, 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0x00, // .X....p......UU. + 0x00, 0x62, 0x10, 0x00, 0x03, 0xc2, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, // .b...........e.. + 0x03, 0xf2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, // .. ......h...... + 0x00, 0x31, 0x00, 0x00, 0x08, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, // .1............@. + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x0a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....?.. ........ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x10, 0x10, // .....".......:.. + 0x80, 0x41, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, // .A........@..... + 0x3f, 0x37, 0x00, 0x00, 0x09, 0x22, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, // ?7...".......... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x10, 0x10, // .............:.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x12, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .....6.......... + 0x00, 0x2a, 0x10, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x8b, 0xc2, 0x00, 0x00, // .*.......E...... + 0x80, 0x43, 0x55, 0x15, 0x00, 0xf2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, // .CU..........F.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x7e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x10, // .....F~.......`. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x07, 0x12, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, // .....1.......... + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // ..@......:...... + 0x00, 0x0e, 0x00, 0x00, 0x07, 0xe2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x09, 0x10, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x0f, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x07, 0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x96, 0x07, 0x10, 0x00, 0x01, 0x00, 0x00, // .r.............. + 0x00, 0x06, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x08, 0x82, 0x00, 0x10, // .........1...... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....... ........ + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x37, 0x00, 0x00, 0x09, 0x72, 0x20, 0x10, // ..@.....?7...r . + 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0x0f, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, // .............F.. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x02, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, // .....F.......6.. + 0x05, 0x82, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // .. ......:...... + 0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, // .>....... +}; diff --git a/Polyfills/Canvas/Source/Shaders/dxil/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/dxil/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..d957d1882 --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/dxil/fs_canvas_unpremultiply.h @@ -0,0 +1,171 @@ +static const uint8_t fs_canvas_unpremultiply_dxil[2681] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, // .......u_canvasC + 0x6f, 0x70, 0x79, 0x12, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, // opy...........s_ + 0x74, 0x65, 0x78, 0x30, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x34, 0x0a, 0x00, // tex0.........4.. + 0x00, 0x44, 0x58, 0x42, 0x43, 0xff, 0x54, 0x23, 0x24, 0x2c, 0x9c, 0x16, 0x78, 0xd5, 0x31, 0xfa, // .DXBC.T#$,..x.1. + 0x61, 0x24, 0x0d, 0x7c, 0x8b, 0x01, 0x00, 0x00, 0x00, 0x34, 0x0a, 0x00, 0x00, 0x07, 0x00, 0x00, // a$.|.....4...... + 0x00, 0x3c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, // .<...L.......... + 0x00, 0x30, 0x02, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0x80, 0x02, 0x00, 0x00, 0x53, 0x46, 0x49, // .0...d.......SFI + 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x53, 0x47, // 0............ISG + 0x31, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1............... + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // .h.............. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // .t.............. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // .t.............. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, // .............SV_ + 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, // Position.TEXCOOR + 0x44, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x53, 0x47, 0x31, 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // D....OSG14...... + 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .........(...... + 0x00, 0x40, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, // .@.............. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x56, 0x5f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x00, 0x00, // .....SV_Target.. + 0x00, 0x50, 0x53, 0x56, 0x30, 0x18, 0x01, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .PSV0....8...... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, // ..........TEXCOO + 0x52, 0x44, 0x00, 0x54, 0x45, 0x58, 0x43, 0x4f, 0x4f, 0x52, 0x44, 0x00, 0x6d, 0x61, 0x69, 0x6e, // RD.TEXCOORD.main + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x03, 0x03, 0x04, 0x00, // ...........D.... + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x42, 0x00, 0x03, 0x02, 0x00, // ...........B.... + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x62, 0x00, 0x03, 0x02, 0x00, // ...........b.... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x44, 0x10, 0x03, 0x00, 0x00, // ...........D.... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, // ................ + 0x00, 0x49, 0x4c, 0x44, 0x4e, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x33, 0x34, 0x31, // .ILDN,.....$.341 + 0x62, 0x32, 0x37, 0x37, 0x37, 0x35, 0x36, 0x30, 0x32, 0x39, 0x64, 0x32, 0x34, 0x63, 0x30, 0x37, // b277756029d24c07 + 0x61, 0x31, 0x61, 0x62, 0x61, 0x32, 0x35, 0x33, 0x65, 0x30, 0x31, 0x33, 0x64, 0x2e, 0x70, 0x64, // a1aba253e013d.pd + 0x62, 0x00, 0x00, 0x00, 0x00, 0x48, 0x41, 0x53, 0x48, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // b....HASH....... + 0x00, 0x34, 0x1b, 0x27, 0x77, 0x56, 0x02, 0x9d, 0x24, 0xc0, 0x7a, 0x1a, 0xba, 0x25, 0x3e, 0x01, // .4.'wV..$.z..%>. + 0x3d, 0x44, 0x58, 0x49, 0x4c, 0xac, 0x07, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xeb, 0x01, 0x00, // =DXIL....`...... + 0x00, 0x44, 0x58, 0x49, 0x4c, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x94, 0x07, 0x00, // .DXIL........... + 0x00, 0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0xe2, 0x01, 0x00, 0x00, 0x0b, 0x82, 0x20, // .BC..!......... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, // ...........#.A.. + 0x49, 0x06, 0x10, 0x32, 0x39, 0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, // I..29....%...... + 0x62, 0x80, 0x18, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0xc4, 0x10, 0x32, 0x14, 0x38, 0x08, 0x18, // b..E.B..B..2.8.. + 0x4b, 0x0a, 0x32, 0x62, 0x88, 0x48, 0x90, 0x14, 0x20, 0x43, 0x46, 0x88, 0xa5, 0x00, 0x19, 0x32, // K.2b.H.. CF....2 + 0x42, 0xe4, 0x48, 0x0e, 0x90, 0x11, 0x23, 0xc4, 0x50, 0x41, 0x51, 0x81, 0x8c, 0xe1, 0x83, 0xe5, // B.H...#.PAQ..... + 0x8a, 0x04, 0x31, 0x46, 0x06, 0x51, 0x18, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1b, 0x8c, 0xe0, // ..1F.Q.......... + 0xff, 0xff, 0xff, 0xff, 0x07, 0x40, 0x02, 0xa8, 0x0d, 0x84, 0xf0, 0xff, 0xff, 0xff, 0xff, 0x03, // .....@.......... + 0x20, 0x6d, 0x30, 0x86, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x09, 0xa8, 0x00, 0x49, 0x18, 0x00, // m0..........I.. + 0x00, 0x03, 0x00, 0x00, 0x00, 0x13, 0x82, 0x60, 0x42, 0x20, 0x4c, 0x08, 0x06, 0x00, 0x00, 0x00, // .......`B L..... + 0x00, 0x89, 0x20, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x32, 0x22, 0x88, 0x09, 0x20, 0x64, 0x85, // .. ..P...2".. d. + 0x04, 0x13, 0x23, 0xa4, 0x84, 0x04, 0x13, 0x23, 0xe3, 0x84, 0xa1, 0x90, 0x14, 0x12, 0x4c, 0x8c, // ..#....#......L. + 0x8c, 0x0b, 0x84, 0xc4, 0x4c, 0x10, 0x80, 0xc1, 0x08, 0x40, 0x09, 0x00, 0x0a, 0x66, 0x00, 0xe6, // ....L....@...f.. + 0x08, 0xc0, 0x60, 0x8e, 0x00, 0x29, 0xc6, 0x40, 0x10, 0x44, 0x41, 0x90, 0x51, 0x0c, 0x80, 0x20, // ..`..).@.DA.Q.. + 0x88, 0x62, 0x20, 0xe4, 0xa6, 0xe1, 0xf2, 0x27, 0xec, 0x21, 0x24, 0x7f, 0x25, 0xa4, 0x95, 0x98, // .b ....'.!$.%... + 0xfc, 0xe2, 0xb6, 0x51, 0x31, 0x0c, 0xc3, 0x40, 0x50, 0x71, 0xcf, 0x70, 0xf9, 0x13, 0xf6, 0x10, // ...Q1..@Pq.p.... + 0x92, 0x1f, 0x02, 0xcd, 0xb0, 0x10, 0x28, 0x58, 0x0a, 0xa3, 0x10, 0x0c, 0x33, 0x0c, 0xc3, 0x40, // ......(X....3..@ + 0x10, 0xc4, 0x40, 0xcd, 0x51, 0xc3, 0xe5, 0x4f, 0xd8, 0x43, 0x48, 0x3e, 0xb7, 0x51, 0xc5, 0x4a, // ..@.Q..O.CH>.Q.J + 0x4c, 0x7e, 0x71, 0xdb, 0x88, 0x18, 0x86, 0x61, 0x28, 0xc4, 0x43, 0x30, 0x04, 0x41, 0x73, 0x04, // L~q....a(.C0.As. + 0x41, 0x31, 0x18, 0xa2, 0x20, 0x08, 0x89, 0xa6, 0x81, 0x80, 0x61, 0x04, 0x62, 0x98, 0xa9, 0x0d, // A1.. .....a.b... + 0xc6, 0x81, 0x1d, 0xc2, 0x61, 0x1e, 0xe6, 0xc1, 0x0d, 0x68, 0xa1, 0x1c, 0xf0, 0x81, 0x1e, 0xea, // ....a....h...... + 0x41, 0x1e, 0xca, 0x41, 0x0e, 0x48, 0x81, 0x0f, 0xec, 0xa1, 0x1c, 0xc6, 0x81, 0x1e, 0xde, 0x41, // A..A.H.........A + 0x1e, 0xf8, 0xc0, 0x1c, 0xd8, 0xe1, 0x1d, 0xc2, 0x81, 0x1e, 0xd8, 0x00, 0x0c, 0xe8, 0xc0, 0x0f, // ................ + 0xc0, 0xc0, 0x0f, 0xf4, 0x40, 0x0f, 0xda, 0x21, 0x1d, 0xe0, 0x61, 0x1e, 0x7e, 0x81, 0x1e, 0xf2, // ....@..!..a.~... + 0x01, 0x1e, 0xca, 0x01, 0x05, 0xc4, 0x4c, 0x62, 0x30, 0x0e, 0xec, 0x10, 0x0e, 0xf3, 0x30, 0x0f, // ......Lb0.....0. + 0x6e, 0x40, 0x0b, 0xe5, 0x80, 0x0f, 0xf4, 0x50, 0x0f, 0xf2, 0x50, 0x0e, 0x72, 0x40, 0x0a, 0x7c, // n@.....P..P.r@.| + 0x60, 0x0f, 0xe5, 0x30, 0x0e, 0xf4, 0xf0, 0x0e, 0xf2, 0xc0, 0x07, 0xe6, 0xc0, 0x0e, 0xef, 0x10, // `..0............ + 0x0e, 0xf4, 0xc0, 0x06, 0x60, 0x40, 0x07, 0x7e, 0x00, 0x06, 0x7e, 0x80, 0x04, 0xeb, 0x22, 0x6c, // ....`@.~..~..."l + 0x26, 0x84, 0x0c, 0x4e, 0x81, 0x1d, 0xde, 0x41, 0x1c, 0xc2, 0x81, 0x1d, 0xe6, 0x01, 0x05, 0x16, // &..N...A........ + 0x69, 0x37, 0x49, 0x53, 0x44, 0x09, 0x93, 0xcf, 0x02, 0xcc, 0xb3, 0x10, 0x11, 0x3b, 0x01, 0x13, // i7ISD........;.. + 0x81, 0x02, 0x82, 0xb8, 0x54, 0x20, 0xa6, 0x00, 0x00, 0x13, 0x14, 0x72, 0xc0, 0x87, 0x74, 0x60, // ....T .....r..t` + 0x87, 0x36, 0x68, 0x87, 0x79, 0x68, 0x03, 0x72, 0xc0, 0x87, 0x0d, 0xaf, 0x50, 0x0e, 0x6d, 0xd0, // .6h.yh.r....P.m. + 0x0e, 0x7a, 0x50, 0x0e, 0x6d, 0x00, 0x0f, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, // .zP.m..z0.r..s . + 0x6d, 0x90, 0x0e, 0x71, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x78, 0xa0, 0x07, 0x73, // m..q..s .m..x..s + 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe9, 0x30, // .m..q`.z0.r...0 + 0x07, 0x72, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x90, 0x0e, 0x76, 0x40, 0x07, 0x7a, 0x60, 0x07, // .r..s .m..v@.z`. + 0x74, 0xd0, 0x06, 0xe6, 0x10, 0x07, 0x76, 0xa0, 0x07, 0x73, 0x20, 0x07, 0x6d, 0x60, 0x0e, 0x73, // t.....v..s .m`.s + 0x20, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xd0, 0x06, 0xe6, 0x60, 0x07, 0x74, 0xa0, 0x07, 0x76, 0x40, // .z0.r...`.t..v@ + 0x07, 0x6d, 0xe0, 0x0e, 0x78, 0xa0, 0x07, 0x71, 0x60, 0x07, 0x7a, 0x30, 0x07, 0x72, 0xa0, 0x07, // .m..x..q`.z0.r.. + 0x76, 0x40, 0x07, 0x43, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // v@.C............ + 0x86, 0x3c, 0x06, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x79, 0x10, // .<............y. + 0x20, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xf2, 0x34, 0x40, 0x00, 0x0c, // ...........4@.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xe4, 0x81, 0x80, 0x00, 0x18, 0x00, 0x00, 0x00, // .......0........ + 0x00, 0x00, 0x00, 0x00, 0x60, 0xc8, 0x33, 0x01, 0x01, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ....`.3..0...... + 0x00, 0x40, 0x16, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x1e, 0x98, 0x14, 0x19, 0x11, 0x4c, // .@.......2.....L + 0x90, 0x8c, 0x09, 0x26, 0x47, 0xc6, 0x04, 0x43, 0x22, 0x4a, 0x60, 0x04, 0xa0, 0x28, 0x8a, 0xa1, // ...&G..C"J`..(.. + 0x08, 0x4a, 0xa2, 0x40, 0xca, 0xa0, 0x3c, 0xa8, 0x28, 0x89, 0x32, 0x28, 0x84, 0x11, 0x80, 0x22, // .J.@..<.(.2(..." + 0x28, 0x10, 0xca, 0x66, 0x00, 0x68, 0x9b, 0x01, 0xa0, 0x6e, 0x06, 0x80, 0xbc, 0xb1, 0x14, 0x84, // (..f.h...n...... + 0x00, 0x00, 0x00, 0x00, 0x80, 0xe7, 0x01, 0x00, 0x00, 0x79, 0x18, 0x00, 0x00, 0x67, 0x00, 0x00, // .........y...g.. + 0x00, 0x1a, 0x03, 0x4c, 0x90, 0x46, 0x02, 0x13, 0xc4, 0x8f, 0x0c, 0x6f, 0xec, 0xed, 0x4d, 0x0c, // ...L.F.....o..M. + 0x24, 0xc6, 0x25, 0x06, 0xc6, 0x45, 0xc6, 0x06, 0xa6, 0xc6, 0x65, 0xe6, 0x06, 0x04, 0x65, 0x8c, // $.%..E....e...e. + 0x86, 0x0c, 0xc7, 0x8c, 0xc6, 0x2c, 0x27, 0x27, 0x65, 0x43, 0x10, 0x4c, 0x10, 0x08, 0x63, 0x82, // .....,''eC.L..c. + 0x40, 0x1c, 0x1b, 0x84, 0x81, 0x98, 0x20, 0x10, 0xc8, 0x06, 0x61, 0x30, 0x28, 0xc0, 0xcd, 0x4d, // @..... ...a0(..M + 0x10, 0x88, 0x64, 0xc3, 0x80, 0x24, 0xc4, 0x04, 0x21, 0xab, 0x08, 0x4c, 0x10, 0x08, 0x65, 0x82, // ..d..$..!..L..e. + 0x40, 0x2c, 0x1b, 0x04, 0xc2, 0xd9, 0x90, 0x10, 0x0b, 0x43, 0x10, 0x43, 0x43, 0x3c, 0x1b, 0x02, // @,.......C.CC<.. + 0x68, 0x82, 0xb0, 0x59, 0x13, 0x04, 0x82, 0xd9, 0x80, 0x10, 0x12, 0x43, 0x10, 0xc3, 0x04, 0x6c, // h..Y.......C...l + 0x08, 0xa8, 0x09, 0x42, 0x77, 0x6d, 0x40, 0x08, 0x8b, 0x21, 0x88, 0x81, 0x00, 0x36, 0x04, 0xd7, // ...Bwm@..!...6.. + 0x06, 0x22, 0x02, 0x2a, 0x6c, 0x82, 0xe0, 0x61, 0x1b, 0x02, 0x6d, 0x82, 0x20, 0x00, 0x24, 0xda, // .".*l..a..m. .$. + 0xc2, 0xd2, 0xdc, 0xb8, 0x4c, 0x59, 0x7d, 0x41, 0xbd, 0xcd, 0xa5, 0xd1, 0xa5, 0xbd, 0xb9, 0x4d, // ....LY}A.......M + 0x10, 0x8a, 0x67, 0x82, 0x50, 0x40, 0x1b, 0x02, 0x62, 0x82, 0x50, 0x44, 0x13, 0x84, 0x42, 0xda, // ..g.P@..b.PD..B. + 0xb0, 0x10, 0xde, 0x07, 0x06, 0x61, 0x20, 0x06, 0x83, 0x18, 0x10, 0x63, 0x00, 0x10, 0xa1, 0x2a, // .....a ....c...* + 0xc2, 0x1a, 0x7a, 0x7a, 0x92, 0x22, 0x9a, 0x20, 0x14, 0xd3, 0x86, 0x65, 0x28, 0x83, 0x6f, 0x0c, // ..zz.". ...e(.o. + 0xc2, 0xc0, 0x0c, 0x06, 0x33, 0x18, 0xc6, 0x00, 0xd8, 0x10, 0x0c, 0x13, 0x04, 0xa2, 0xd9, 0x20, // ....3.......... + 0xa4, 0x41, 0x1a, 0x6c, 0x58, 0x9a, 0x32, 0xf8, 0xc6, 0x00, 0x0d, 0xcc, 0x60, 0x30, 0x83, 0xc1, // .A.lX.2.....`0.. + 0x0c, 0xd4, 0x60, 0xc3, 0x40, 0x06, 0x67, 0xb0, 0x06, 0x4c, 0xa6, 0xac, 0xbe, 0xa8, 0xc2, 0xe4, // ..`.@.g..L...... + 0xce, 0xca, 0xe8, 0x26, 0x08, 0x05, 0x35, 0x41, 0x20, 0x9c, 0x0d, 0x42, 0x1a, 0xbc, 0xc1, 0x86, // ...&..5A ..B.... + 0x85, 0x68, 0x83, 0xcf, 0x0d, 0xc2, 0x60, 0x0c, 0x06, 0x31, 0x20, 0xc6, 0x00, 0x0e, 0x36, 0x04, // .h....`..1 ...6. + 0x71, 0xb0, 0x61, 0x60, 0x03, 0x39, 0x00, 0x36, 0x14, 0x5c, 0x37, 0x07, 0x19, 0x50, 0x85, 0x8d, // q.a`.9.6..7..P.. + 0xcd, 0xae, 0xcd, 0x25, 0x8d, 0xac, 0xcc, 0x8d, 0x6e, 0x4a, 0x10, 0x54, 0x21, 0xc3, 0x73, 0xb1, // ...%....nJ.T!.s. + 0x2b, 0x93, 0x9b, 0x4b, 0x7b, 0x73, 0x9b, 0x12, 0x10, 0x4d, 0xc8, 0xf0, 0x5c, 0xec, 0xc2, 0xd8, // +..K{s...M...... + 0xec, 0xca, 0xe4, 0xa6, 0x04, 0x46, 0x1d, 0x32, 0x3c, 0x97, 0x39, 0xb4, 0x30, 0xb2, 0x32, 0xb9, // .....F.2<.9.0.2. + 0xa6, 0x37, 0xb2, 0x32, 0xb6, 0x29, 0x41, 0x52, 0x86, 0x0c, 0xcf, 0x45, 0xae, 0x6c, 0xee, 0xad, // .7.2.)AR...E.l.. + 0x4e, 0x6e, 0xac, 0x6c, 0x6e, 0x4a, 0x80, 0xd5, 0x21, 0xc3, 0x73, 0xb1, 0x4b, 0x2b, 0xbb, 0x4b, // Nn.lnJ..!.s.K+.K + 0x22, 0x9b, 0xa2, 0x0b, 0xa3, 0x2b, 0x9b, 0x12, 0x68, 0x75, 0xc8, 0xf0, 0x5c, 0xca, 0xdc, 0xe8, // "....+..hu...... + 0xe4, 0xf2, 0xa0, 0xde, 0xd2, 0xdc, 0xe8, 0xe6, 0xa6, 0x04, 0x73, 0x00, 0x00, 0x79, 0x18, 0x00, // ..........s..y.. + 0x00, 0x51, 0x00, 0x00, 0x00, 0x33, 0x08, 0x80, 0x1c, 0xc4, 0xe1, 0x1c, 0x66, 0x14, 0x01, 0x3d, // .Q...3......f..= + 0x88, 0x43, 0x38, 0x84, 0xc3, 0x8c, 0x42, 0x80, 0x07, 0x79, 0x78, 0x07, 0x73, 0x98, 0x71, 0x0c, // .C8...B..yx.s.q. + 0xe6, 0x00, 0x0f, 0xed, 0x10, 0x0e, 0xf4, 0x80, 0x0e, 0x33, 0x0c, 0x42, 0x1e, 0xc2, 0xc1, 0x1d, // .........3.B.... + 0xce, 0xa1, 0x1c, 0x66, 0x30, 0x05, 0x3d, 0x88, 0x43, 0x38, 0x84, 0x83, 0x1b, 0xcc, 0x03, 0x3d, // ...f0.=.C8.....= + 0xc8, 0x43, 0x3d, 0x8c, 0x03, 0x3d, 0xcc, 0x78, 0x8c, 0x74, 0x70, 0x07, 0x7b, 0x08, 0x07, 0x79, // .C=..=.x.tp.{..y + 0x48, 0x87, 0x70, 0x70, 0x07, 0x7a, 0x70, 0x03, 0x76, 0x78, 0x87, 0x70, 0x20, 0x87, 0x19, 0xcc, // H.pp.zp.vx.p ... + 0x11, 0x0e, 0xec, 0x90, 0x0e, 0xe1, 0x30, 0x0f, 0x6e, 0x30, 0x0f, 0xe3, 0xf0, 0x0e, 0xf0, 0x50, // ......0.n0.....P + 0x0e, 0x33, 0x10, 0xc4, 0x1d, 0xde, 0x21, 0x1c, 0xd8, 0x21, 0x1d, 0xc2, 0x61, 0x1e, 0x66, 0x30, // .3....!..!..a.f0 + 0x89, 0x3b, 0xbc, 0x83, 0x3b, 0xd0, 0x43, 0x39, 0xb4, 0x03, 0x3c, 0xbc, 0x83, 0x3c, 0x84, 0x03, // .;..;.C9..<..<.. + 0x3b, 0xcc, 0xf0, 0x14, 0x76, 0x60, 0x07, 0x7b, 0x68, 0x07, 0x37, 0x68, 0x87, 0x72, 0x68, 0x07, // ;...v`.{h.7h.rh. + 0x37, 0x80, 0x87, 0x70, 0x90, 0x87, 0x70, 0x60, 0x07, 0x76, 0x28, 0x07, 0x76, 0xf8, 0x05, 0x76, // 7..p..p`.v(.v..v + 0x78, 0x87, 0x77, 0x80, 0x87, 0x5f, 0x08, 0x87, 0x71, 0x18, 0x87, 0x72, 0x98, 0x87, 0x79, 0x98, // x.w.._..q..r..y. + 0x81, 0x2c, 0xee, 0xf0, 0x0e, 0xee, 0xe0, 0x0e, 0xf5, 0xc0, 0x0e, 0xec, 0x30, 0x03, 0x62, 0xc8, // .,..........0.b. + 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xcc, 0xa1, 0x1c, 0xe4, 0xa1, 0x1c, 0xdc, 0x61, 0x1c, 0xca, 0x21, // ............a..! + 0x1c, 0xc4, 0x81, 0x1d, 0xca, 0x61, 0x06, 0xd6, 0x90, 0x43, 0x39, 0xc8, 0x43, 0x39, 0x98, 0x43, // .....a...C9.C9.C + 0x39, 0xc8, 0x43, 0x39, 0xb8, 0xc3, 0x38, 0x94, 0x43, 0x38, 0x88, 0x03, 0x3b, 0x94, 0xc3, 0x2f, // 9.C9..8.C8..;../ + 0xbc, 0x83, 0x3c, 0xfc, 0x82, 0x3b, 0xd4, 0x03, 0x3b, 0xb0, 0xc3, 0x0c, 0xc4, 0x21, 0x07, 0x7c, // ..<..;..;....!.| + 0x70, 0x03, 0x7a, 0x28, 0x87, 0x76, 0x80, 0x87, 0x19, 0xd1, 0x43, 0x0e, 0xf8, 0xe0, 0x06, 0xe4, // p.z(.v....C..... + 0x20, 0x0e, 0xe7, 0xe0, 0x06, 0xf6, 0x10, 0x0e, 0xf2, 0xc0, 0x0e, 0xe1, 0x90, 0x0f, 0xef, 0x50, // ..............P + 0x0f, 0xf4, 0x30, 0x83, 0x81, 0xc8, 0x01, 0x1f, 0xdc, 0x40, 0x1c, 0xe4, 0xa1, 0x1c, 0xc2, 0x61, // ..0......@.....a + 0x1d, 0xdc, 0x40, 0x1c, 0xe4, 0x01, 0x00, 0x00, 0x00, 0x71, 0x20, 0x00, 0x00, 0x18, 0x00, 0x00, // ..@......q ..... + 0x00, 0x46, 0xb0, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x10, 0x50, 0x45, 0x41, 0x44, 0xa5, 0x03, 0x0c, // .F....<..PEAD... + 0x25, 0x61, 0x00, 0x02, 0xe6, 0x17, 0xb7, 0x6d, 0x05, 0xd2, 0x70, 0xf9, 0xce, 0xe3, 0x0b, 0x11, // %a.....m..p..... + 0x01, 0x4c, 0x44, 0x08, 0x34, 0xc3, 0x42, 0x58, 0xc0, 0x34, 0x5c, 0xbe, 0xf3, 0xf8, 0x8b, 0x03, // .LD.4.BX.4...... + 0x0c, 0x62, 0xf3, 0x50, 0x93, 0x5f, 0xdc, 0xb6, 0x0d, 0x40, 0xc3, 0xe5, 0x3b, 0x8f, 0x2f, 0x01, // .b.P._...@..;./. + 0xcc, 0xb3, 0x10, 0x7e, 0x71, 0xdb, 0x26, 0x50, 0x0d, 0x97, 0xef, 0x3c, 0xbe, 0x34, 0x39, 0x11, // ...~q.&P...<.49. + 0x81, 0x52, 0xd3, 0x43, 0x4d, 0x7e, 0x71, 0xdb, 0x06, 0x40, 0x30, 0x00, 0xd2, 0x00, 0x00, 0x00, // .R.CM~q..@0..... + 0x00, 0x61, 0x20, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x13, 0x04, 0x44, 0x2c, 0x10, 0x00, 0x00, // .a ..J.....D,... + 0x00, 0x09, 0x00, 0x00, 0x00, 0x24, 0x8d, 0x00, 0x50, 0x51, 0x02, 0x44, 0x14, 0x5e, 0xd9, 0x95, // .....$..PQ.D.^.. + 0x42, 0xc9, 0xcd, 0x00, 0x14, 0x02, 0x0d, 0x63, 0x04, 0x20, 0x08, 0x82, 0xf8, 0x37, 0x46, 0x00, // B......c. ...7F. + 0x82, 0x20, 0x08, 0x7f, 0x33, 0x00, 0x23, 0x00, 0x00, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x30, // . ..3.#..#....`0 + 0x81, 0xc1, 0x31, 0x79, 0x1e, 0x33, 0x62, 0x90, 0x00, 0x20, 0x08, 0x06, 0x53, 0x18, 0x20, 0xd6, // ..1y.3b.. ..S. . + 0xf7, 0x35, 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x30, 0x89, 0x41, 0x42, 0x81, 0x01, 0x18, 0x38, // .5#....`0.AB...8 + 0x23, 0x06, 0x09, 0x00, 0x82, 0x60, 0x60, 0x9c, 0x01, 0xe2, 0x85, 0x81, 0x95, 0x8c, 0x18, 0x24, // #....``........$ + 0x00, 0x08, 0x82, 0x81, 0x81, 0x06, 0xc9, 0x27, 0x06, 0x8f, 0x32, 0x62, 0x70, 0x00, 0x20, 0x08, // .......'..2bp. . + 0x06, 0x90, 0x19, 0x38, 0xc3, 0x18, 0x8c, 0x26, 0x04, 0xc0, 0x70, 0x44, 0xa0, 0x08, 0x9f, 0x31, // ...8...&..pD...1 + 0x44, 0x7c, 0xa6, 0x1b, 0x82, 0x42, 0x18, 0x31, 0x78, 0x00, 0x10, 0x04, 0x83, 0x86, 0x0d, 0x28, // D|...B.1x......( + 0x25, 0x39, 0x02, 0x86, 0x41, 0x03, 0x34, 0x80, 0x98, 0xd1, 0x84, 0x00, 0x18, 0x4d, 0x10, 0x82, // %9..A.4......M.. + 0xd1, 0x84, 0x41, 0x18, 0x4d, 0x20, 0x86, 0xd1, 0x04, 0x25, 0x18, 0x8e, 0x08, 0x26, 0xe2, 0x9b, // ..A.M ...%...&.. + 0x65, 0x08, 0x86, 0x60, 0x38, 0x62, 0x90, 0x84, 0x6f, 0x96, 0x41, 0x18, 0x02, 0x3b, 0x08, 0xfa, // e..`8b..o.A..;.. + 0xd8, 0x51, 0xd0, 0xc7, 0x0e, 0x83, 0x3e, 0xb3, 0x04, 0xc3, 0x40, 0xc7, 0x40, 0x01, 0x86, 0xc0, // .Q....>...@.@... + 0x06, 0xc1, 0x40, 0xc7, 0x40, 0x01, 0x86, 0xe0, 0x06, 0xc1, 0x40, 0xc7, 0x40, 0x01, 0x86, 0x00, // ..@.@.....@.@... + 0x07, 0xc1, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0x7b, 0x00, 0x06, 0x76, 0x60, 0x07, 0x6b, // ...A.. . {..v`.k + 0x30, 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, 0x01, 0xb2, 0x07, 0x60, 0x60, 0x07, 0x76, 0x30, 0x06, // 0..$......``.v0. + 0xc2, 0x88, 0x41, 0x02, 0x80, 0x20, 0x18, 0x20, 0x7b, 0x00, 0x06, 0x76, 0x60, 0x07, 0x6a, 0x10, // ..A.. . {..v`.j. + 0x8c, 0x18, 0x24, 0x00, 0x08, 0x82, 0x01, 0xb2, 0x07, 0x60, 0x60, 0x07, 0x76, 0xd0, 0x06, 0x0a, // ..$......``.v... + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, // ......... +}; diff --git a/Polyfills/Canvas/Source/Shaders/essl/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/essl/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..65b414906 --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/essl/fs_canvas_unpremultiply.h @@ -0,0 +1,66 @@ +static const uint8_t fs_canvas_unpremultiply_essl[1000] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, // .......u_canvasC + 0x6f, 0x70, 0x79, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, // opy...........s_ + 0x74, 0x65, 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0xa6, 0x03, 0x00, // tex............. + 0x00, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, // .precision mediu + 0x6d, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, // mp float;.precis + 0x69, 0x6f, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x69, 0x6e, 0x74, 0x3b, 0x0a, 0x0a, // ion highp int;.. + 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, // uniform highp ve + 0x63, 0x34, 0x20, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x3b, // c4 u_canvasCopy; + 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x73, // .uniform highp s + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x3b, 0x0a, // ampler2D s_tex;. + 0x0a, 0x69, 0x6e, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, // .in highp vec2 v + 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, // _texcoord0;.layo + 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, // ut(location = 0) + 0x20, 0x6f, 0x75, 0x74, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, // out highp vec4 + 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, // bgfx_FragColor;. + 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, // .void main().{. + 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x32, // highp vec2 _2 + 0x32, 0x20, 0x3d, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, // 2 = v_texcoord0; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, // . highp vec2 + 0x5f, 0x31, 0x31, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, // _119;. if (u_ + 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x78, 0x20, 0x3e, 0x20, 0x30, // canvasCopy.x > 0 + 0x2e, 0x35, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // .5). {. + 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x31, 0x31, // highp vec2 _11 + 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 0 = _22;. + 0x20, 0x5f, 0x31, 0x31, 0x30, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, // _110.y = 1.0 - + 0x5f, 0x32, 0x32, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // _22.y;. _ + 0x31, 0x31, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // 119 = _110;. + 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, // }. else. { + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x39, 0x20, 0x3d, 0x20, // . _119 = + 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, // _22;. }. h + 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x30, 0x20, 0x3d, 0x20, // ighp vec4 _50 = + 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, 0x20, 0x5f, // texture(s_tex, _ + 0x31, 0x31, 0x39, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, // 119);. highp + 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, // vec4 _122;. i + 0x66, 0x20, 0x28, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, // f (u_canvasCopy. + 0x79, 0x20, 0x3c, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // y < 0.5). {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x66, 0x6c, 0x6f, // highp flo + 0x61, 0x74, 0x20, 0x5f, 0x35, 0x38, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x30, 0x2e, 0x77, 0x3b, 0x0a, // at _58 = _50.w;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, // highp ve + 0x63, 0x33, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // c3 _120;. + 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x35, 0x38, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, // if (_58 > 0.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x30, // _120 = _50 + 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x35, 0x38, 0x29, // .xyz / vec3(_58) + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // ;. }. + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // {. _ + 0x31, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x3b, // 120 = vec3(0.0); + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // . }. + 0x20, 0x20, 0x20, 0x68, 0x69, 0x67, 0x68, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, // highp vec4 _1 + 0x31, 0x34, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // 14 = _50;. + 0x20, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x2e, // _114.x = _120. + 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x2e, // x;. _114. + 0x79, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, // y = _120.y;. + 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, // _114.z = _12 + 0x30, 0x2e, 0x7a, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x32, // 0.z;. _12 + 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, // 2 = _114;. }. + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, // else. {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x35, // _122 = _5 + 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, // 0;. }. bgf + 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x5f, 0x31, // x_FragColor = _1 + 0x32, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, // 22;.}... +}; diff --git a/Polyfills/Canvas/Source/Shaders/fs_canvas_unpremultiply.sc b/Polyfills/Canvas/Source/Shaders/fs_canvas_unpremultiply.sc new file mode 100644 index 000000000..259d3ab11 --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/fs_canvas_unpremultiply.sc @@ -0,0 +1,21 @@ +$input v_position, v_texcoord0 + +#include "./common.sh" + +SAMPLER2D(s_tex, 0); +uniform vec4 u_canvasCopy; + +void main() +{ + vec2 uv = v_texcoord0; + if (u_canvasCopy.x > 0.5) + { + uv.y = 1.0 - uv.y; + } + vec4 color = texture2D(s_tex, uv); + if (u_canvasCopy.y < 0.5) + { + color.rgb = color.a > 0.0 ? color.rgb / color.a : vec3_splat(0.0); + } + gl_FragColor = color; +} diff --git a/Polyfills/Canvas/Source/Shaders/glsl/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/glsl/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..a0909743a --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/glsl/fs_canvas_unpremultiply.h @@ -0,0 +1,59 @@ +static const uint8_t fs_canvas_unpremultiply_glsl[882] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, // .......u_canvasC + 0x6f, 0x70, 0x79, 0x02, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, // opy...........s_ + 0x74, 0x65, 0x78, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x30, 0x03, 0x00, // tex..........0.. + 0x00, 0x0a, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x75, // ..uniform vec4 u + 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x3b, 0x0a, 0x75, 0x6e, 0x69, // _canvasCopy;.uni + 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x73, // form sampler2D s + 0x5f, 0x74, 0x65, 0x78, 0x3b, 0x0a, 0x0a, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x76, // _tex;..in vec2 v + 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x6c, 0x61, 0x79, 0x6f, // _texcoord0;.layo + 0x75, 0x74, 0x28, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x29, // ut(location = 0) + 0x20, 0x6f, 0x75, 0x74, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, // out vec4 bgfx_F + 0x72, 0x61, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x0a, 0x76, 0x6f, 0x69, 0x64, 0x20, // ragColor;..void + 0x6d, 0x61, 0x69, 0x6e, 0x28, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, // main().{. vec + 0x32, 0x20, 0x5f, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x76, 0x5f, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, // 2 _22 = v_texcoo + 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x31, // rd0;. vec2 _1 + 0x31, 0x39, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, 0x5f, 0x63, 0x61, // 19;. if (u_ca + 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x78, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x35, // nvasCopy.x > 0.5 + 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // ). {. + 0x76, 0x65, 0x63, 0x32, 0x20, 0x5f, 0x31, 0x31, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x32, 0x3b, // vec2 _110 = _22; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x30, 0x2e, 0x79, 0x20, // . _110.y + 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x5f, 0x32, 0x32, 0x2e, 0x79, 0x3b, 0x0a, 0x20, // = 1.0 - _22.y;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x31, // _119 = _1 + 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, // 10;. }. el + 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // se. {. + 0x20, 0x5f, 0x31, 0x31, 0x39, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, // _119 = _22;. + 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x35, 0x30, 0x20, // }. vec4 _50 + 0x3d, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2c, // = texture(s_tex, + 0x20, 0x5f, 0x31, 0x31, 0x39, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, // _119);. vec4 + 0x20, 0x5f, 0x31, 0x32, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x75, // _122;. if (u + 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x79, 0x20, 0x3c, 0x20, // _canvasCopy.y < + 0x30, 0x2e, 0x35, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // 0.5). {. + 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x5f, 0x35, 0x38, 0x20, 0x3d, 0x20, 0x5f, // float _58 = _ + 0x35, 0x30, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, // 50.w;. ve + 0x63, 0x33, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // c3 _120;. + 0x20, 0x69, 0x66, 0x20, 0x28, 0x5f, 0x35, 0x38, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x0a, // if (_58 > 0.0). + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // {. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x30, // _120 = _50 + 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2f, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x5f, 0x35, 0x38, 0x29, // .xyz / vec3(_58) + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, // ;. }. + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // else. + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // {. _ + 0x31, 0x32, 0x30, 0x20, 0x3d, 0x20, 0x76, 0x65, 0x63, 0x33, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x3b, // 120 = vec3(0.0); + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, // . }. + 0x20, 0x20, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x20, 0x3d, 0x20, 0x5f, // vec4 _114 = _ + 0x35, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x34, // 50;. _114 + 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, // .x = _120.x;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x31, 0x34, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x5f, 0x31, // _114.y = _1 + 0x32, 0x30, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, // 20.y;. _1 + 0x31, 0x34, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, 0x30, 0x2e, 0x7a, 0x3b, 0x0a, 0x20, // 14.z = _120.z;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x31, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x31, // _122 = _1 + 0x31, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, // 14;. }. el + 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // se. {. + 0x20, 0x5f, 0x31, 0x32, 0x32, 0x20, 0x3d, 0x20, 0x5f, 0x35, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, // _122 = _50;. + 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, // }. bgfx_Frag + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x32, 0x32, 0x3b, 0x0a, 0x7d, 0x0a, // Color = _122;.}. + 0x0a, 0x00, // .. +}; diff --git a/Polyfills/Canvas/Source/Shaders/metal/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/metal/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..b9570abef --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/metal/fs_canvas_unpremultiply.h @@ -0,0 +1,82 @@ +static const uint8_t fs_canvas_unpremultiply_mtl[1259] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, // .......s_texSamp + 0x6c, 0x65, 0x72, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x73, 0x5f, // ler...........s_ + 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x11, 0x01, 0xff, 0xff, 0x01, 0x00, // texTexture...... + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, // .....u_canvasCop + 0x79, 0x12, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, 0x74, 0x65, // y...........s_te + 0x78, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x78, 0x04, 0x00, 0x00, 0x23, // x..........x...# + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x3c, 0x6d, 0x65, 0x74, 0x61, 0x6c, 0x5f, 0x73, // include .#include + 0x3c, 0x73, 0x69, 0x6d, 0x64, 0x2f, 0x73, 0x69, 0x6d, 0x64, 0x2e, 0x68, 0x3e, 0x0a, 0x0a, 0x75, // ..u + 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x6d, // sing namespace m + 0x65, 0x74, 0x61, 0x6c, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x5f, 0x47, // etal;..struct _G + 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, // lobal.{. floa + 0x74, 0x34, 0x20, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, 0x6f, 0x70, 0x79, 0x3b, // t4 u_canvasCopy; + 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, // .};..struct xlat + 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x0a, 0x7b, 0x0a, 0x20, 0x20, // MtlMain_out.{. + 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, // float4 bgfx_Fr + 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x5b, 0x5b, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x28, // agData0 [[color( + 0x30, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, // 0)]];.};..struct + 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x0a, // xlatMtlMain_in. + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x76, 0x5f, 0x74, // {. float2 v_t + 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x20, 0x5b, 0x5b, 0x75, 0x73, 0x65, 0x72, 0x28, // excoord0 [[user( + 0x6c, 0x6f, 0x63, 0x6e, 0x31, 0x29, 0x5d, 0x5d, 0x3b, 0x0a, 0x7d, 0x3b, 0x0a, 0x0a, 0x66, 0x72, // locn1)]];.};..fr + 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, // agment xlatMtlMa + 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, // in_out xlatMtlMa + 0x69, 0x6e, 0x28, 0x78, 0x6c, 0x61, 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x69, // in(xlatMtlMain_i + 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x5b, 0x5b, 0x73, 0x74, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x5d, // n in [[stage_in] + 0x5d, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x5f, 0x47, 0x6c, 0x6f, // ], constant _Glo + 0x62, 0x61, 0x6c, 0x26, 0x20, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x20, 0x5b, 0x5b, 0x62, 0x75, // bal& _mtl_u [[bu + 0x66, 0x66, 0x65, 0x72, 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, // ffer(0)]], textu + 0x72, 0x65, 0x32, 0x64, 0x3c, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3e, 0x20, 0x73, 0x5f, 0x74, 0x65, // re2d s_te + 0x78, 0x20, 0x5b, 0x5b, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x28, 0x30, 0x29, 0x5d, 0x5d, // x [[texture(0)]] + 0x2c, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, // , sampler s_texS + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x20, 0x5b, 0x5b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // ampler [[sampler + 0x28, 0x30, 0x29, 0x5d, 0x5d, 0x29, 0x0a, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x78, 0x6c, 0x61, // (0)]]).{. xla + 0x74, 0x4d, 0x74, 0x6c, 0x4d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x20, 0x6f, 0x75, 0x74, // tMtlMain_out out + 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, // = {};. float + 0x32, 0x20, 0x5f, 0x31, 0x33, 0x38, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x2e, 0x76, 0x5f, 0x74, 0x65, // 2 _138 = in.v_te + 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // xcoord0;. flo + 0x61, 0x74, 0x32, 0x20, 0x5f, 0x32, 0x35, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, // at2 _250;. if + 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, // (_mtl_u.u_canva + 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x78, 0x20, 0x3e, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x0a, 0x20, // sCopy.x > 0.5). + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // {. flo + 0x61, 0x74, 0x32, 0x20, 0x5f, 0x32, 0x34, 0x31, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x33, 0x38, 0x3b, // at2 _241 = _138; + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x32, 0x34, 0x31, 0x2e, 0x79, 0x20, // . _241.y + 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x20, 0x2d, 0x20, 0x5f, 0x31, 0x33, 0x38, 0x2e, 0x79, 0x3b, 0x0a, // = 1.0 - _138.y;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x32, 0x35, 0x30, 0x20, 0x3d, 0x20, 0x5f, // _250 = _ + 0x32, 0x34, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, // 241;. }. e + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. {. + 0x20, 0x20, 0x5f, 0x32, 0x35, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x31, 0x33, 0x38, 0x3b, 0x0a, 0x20, // _250 = _138;. + 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, // }. float4 + 0x5f, 0x32, 0x32, 0x38, 0x20, 0x3d, 0x20, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x2e, 0x73, 0x61, 0x6d, // _228 = s_tex.sam + 0x70, 0x6c, 0x65, 0x28, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, // ple(s_texSampler + 0x2c, 0x20, 0x5f, 0x32, 0x35, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // , _250);. flo + 0x61, 0x74, 0x34, 0x20, 0x5f, 0x32, 0x35, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, // at4 _251;. if + 0x20, 0x28, 0x5f, 0x6d, 0x74, 0x6c, 0x5f, 0x75, 0x2e, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, // (_mtl_u.u_canva + 0x73, 0x43, 0x6f, 0x70, 0x79, 0x2e, 0x79, 0x20, 0x3c, 0x20, 0x30, 0x2e, 0x35, 0x29, 0x0a, 0x20, // sCopy.y < 0.5). + 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, // {. flo + 0x61, 0x74, 0x20, 0x5f, 0x31, 0x39, 0x37, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x32, 0x38, 0x2e, 0x77, // at _197 = _228.w + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x33, // ;. float3 + 0x20, 0x5f, 0x32, 0x30, 0x37, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x28, 0x66, // _207 = select(f + 0x6c, 0x6f, 0x61, 0x74, 0x33, 0x28, 0x30, 0x2e, 0x30, 0x29, 0x2c, 0x20, 0x5f, 0x32, 0x32, 0x38, // loat3(0.0), _228 + 0x2e, 0x78, 0x79, 0x7a, 0x20, 0x2f, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x33, 0x28, 0x5f, 0x31, // .xyz / float3(_1 + 0x39, 0x37, 0x29, 0x2c, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x33, 0x28, 0x5f, 0x31, 0x39, 0x37, 0x20, // 97), bool3(_197 + 0x3e, 0x20, 0x30, 0x2e, 0x30, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // > 0.0));. + 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x5f, 0x32, 0x34, 0x35, 0x20, 0x3d, 0x20, 0x5f, // float4 _245 = _ + 0x32, 0x32, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x32, 0x34, // 228;. _24 + 0x35, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x30, 0x37, 0x2e, 0x78, 0x3b, 0x0a, 0x20, 0x20, // 5.x = _207.x;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x32, 0x34, 0x35, 0x2e, 0x79, 0x20, 0x3d, 0x20, 0x5f, // _245.y = _ + 0x32, 0x30, 0x37, 0x2e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, // 207.y;. _ + 0x32, 0x34, 0x35, 0x2e, 0x7a, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x30, 0x37, 0x2e, 0x7a, 0x3b, 0x0a, // 245.z = _207.z;. + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5f, 0x32, 0x35, 0x31, 0x20, 0x3d, 0x20, 0x5f, // _251 = _ + 0x32, 0x34, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, // 245;. }. e + 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // lse. {. + 0x20, 0x20, 0x5f, 0x32, 0x35, 0x31, 0x20, 0x3d, 0x20, 0x5f, 0x32, 0x32, 0x38, 0x3b, 0x0a, 0x20, // _251 = _228;. + 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x75, 0x74, 0x2e, 0x62, 0x67, 0x66, // }. out.bgf + 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x20, 0x3d, 0x20, 0x5f, 0x32, // x_FragData0 = _2 + 0x35, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, // 51;. return o + 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x00, 0x00, 0x30, 0x00, // ut;.}....0. +}; diff --git a/Polyfills/Canvas/Source/Shaders/spirv/fs_canvas_unpremultiply.h b/Polyfills/Canvas/Source/Shaders/spirv/fs_canvas_unpremultiply.h new file mode 100644 index 000000000..125c329c4 --- /dev/null +++ b/Polyfills/Canvas/Source/Shaders/spirv/fs_canvas_unpremultiply.h @@ -0,0 +1,116 @@ +static const uint8_t fs_canvas_unpremultiply_spv[1793] = +{ + 0x46, 0x53, 0x48, 0x0c, 0xcf, 0xda, 0x1b, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // FSH............. + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0c, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x43, // .......u_canvasC + 0x6f, 0x70, 0x79, 0x12, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x73, 0x5f, // opy...........s_ + 0x74, 0x65, 0x78, 0x30, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x29, 0x00, 0xbc, 0x06, 0x00, // tex0.......).... + 0x00, 0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x08, 0x00, 0xfc, 0x00, 0x00, // ...#............ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, // ................ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, // .....GLSL.std.45 + 0x30, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // 0............... + 0x00, 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, // .............mai + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, // n............... + 0x00, 0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x05, 0x00, 0x00, // ................ + 0x00, 0xf4, 0x01, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, // .............mai + 0x6e, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x28, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x74, // n........(...s_t + 0x65, 0x78, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, // exSampler....... + 0x00, 0x2b, 0x00, 0x00, 0x00, 0x73, 0x5f, 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x74, 0x75, 0x72, // .+...s_texTextur + 0x65, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x55, 0x6e, 0x69, // e........K...Uni + 0x66, 0x6f, 0x72, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x07, // formBlock....... + 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x5f, 0x63, 0x61, 0x6e, 0x76, 0x61, // .K.......u_canva + 0x73, 0x43, 0x6f, 0x70, 0x79, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00, 0x4d, 0x00, 0x00, // sCopy........M.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x89, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x74, // .............v_t + 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x30, 0x00, 0x05, 0x00, 0x06, 0x00, 0x96, 0x00, 0x00, // excoord0........ + 0x00, 0x62, 0x67, 0x66, 0x78, 0x5f, 0x46, 0x72, 0x61, 0x67, 0x44, 0x61, 0x74, 0x61, 0x30, 0x00, // .bgfx_FragData0. + 0x00, 0x47, 0x00, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, // .G...(...!...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...(..."...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // .G...+...!...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...+..."...... + 0x00, 0x47, 0x00, 0x03, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, // .G...K.......H.. + 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .K.......#...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .G...M...!...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G...M..."...... + 0x00, 0x47, 0x00, 0x04, 0x00, 0x89, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // .G.............. + 0x00, 0x47, 0x00, 0x04, 0x00, 0x96, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // .G.............. + 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, // .........!...... + 0x00, 0x02, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x02, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, // ................ + 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x08, 0x00, 0x00, // ..... .......... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, // ................ + 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, // ................ + 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // ............. .. + 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, // .'...........;.. + 0x00, 0x27, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // .'...(....... .. + 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, // .*...........;.. + 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, // .*...+.......... + 0x00, 0x2e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // ..... .......+.. + 0x00, 0x2e, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, // .....3.......... + 0x00, 0x37, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .7.......+...... + 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00, 0x4b, 0x00, 0x00, // .F...........K.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ..... ...L...... + 0x00, 0x4b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, // .K...;...L...M.. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, // .........N... .. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, // .....+...N...O.. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // ..... ...P...... + 0x00, 0x07, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, // .....+.......S.. + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x14, 0x00, 0x02, 0x00, 0x54, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, // ....?....T...+.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x2b, 0x00, 0x04, // .....X......?+.. + 0x00, 0x4e, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, // .N...Y.......... + 0x00, 0x75, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // .u...T....... .. + 0x00, 0x85, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, // .............;.. + 0x00, 0x85, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, // ............. .. + 0x00, 0x95, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, // .............;.. + 0x00, 0x95, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x06, // .............,.. + 0x00, 0x14, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, // .........F...F.. + 0x00, 0x46, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, // .F...6.......... + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, // ................ + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, // .=.......)...(.. + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, // .=.......,...+.. + 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, // .=.............. + 0x00, 0x41, 0x00, 0x06, 0x00, 0x50, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x00, // .A...P.......M.. + 0x00, 0x33, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, // .3...O...=...... + 0x00, 0xb5, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xba, 0x00, 0x05, 0x00, 0x54, 0x00, 0x00, // .............T.. + 0x00, 0xb6, 0x00, 0x00, 0x00, 0xb5, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x03, // .........S...... + 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, 0x00, 0xb6, 0x00, 0x00, // ................ + 0x00, 0xb7, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xb7, 0x00, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, // .Q.............. + 0x00, 0x01, 0x00, 0x00, 0x00, 0x83, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, // ................ + 0x00, 0x58, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x52, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, // .X.......R...... + 0x00, 0xf1, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0xf9, 0x00, 0x02, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xbc, 0x00, 0x00, // ................ + 0x00, 0xf5, 0x00, 0x07, 0x00, 0x0b, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, // ................ + 0x00, 0x05, 0x00, 0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0xb7, 0x00, 0x00, 0x00, 0x56, 0x00, 0x05, // .............V.. + 0x00, 0x37, 0x00, 0x00, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, // .7.......,...).. + 0x00, 0x57, 0x00, 0x05, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xe2, 0x00, 0x00, // .W.............. + 0x00, 0xfa, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x50, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, // .....A...P...... + 0x00, 0x4d, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, // .M...3...Y...=.. + 0x00, 0x07, 0x00, 0x00, 0x00, 0xc1, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x05, // ................ + 0x00, 0x54, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc1, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, // .T...........S.. + 0x00, 0xf7, 0x00, 0x03, 0x00, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x04, // ................ + 0x00, 0xc2, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, // ................ + 0x00, 0xc3, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, // .....Q.......... + 0x00, 0xe4, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xba, 0x00, 0x05, 0x00, 0x54, 0x00, 0x00, // .............T.. + 0x00, 0xc6, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x4f, 0x00, 0x08, // .........F...O.. + 0x00, 0x14, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x00, 0x06, // .............P.. + 0x00, 0x14, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, 0x00, 0xc5, 0x00, 0x00, // ................ + 0x00, 0xc5, 0x00, 0x00, 0x00, 0x88, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, // ................ + 0x00, 0xc8, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x50, 0x00, 0x06, 0x00, 0x75, 0x00, 0x00, // .........P...u.. + 0x00, 0xce, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, // ................ + 0x00, 0xa9, 0x00, 0x06, 0x00, 0x14, 0x00, 0x00, 0x00, 0xcf, 0x00, 0x00, 0x00, 0xce, 0x00, 0x00, // ................ + 0x00, 0xcc, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, // .........Q...... + 0x00, 0xd1, 0x00, 0x00, 0x00, 0xcf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x06, // .............R.. + 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x00, 0x00, 0xd1, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, // ................ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, // .....Q.......... + 0x00, 0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x52, 0x00, 0x06, 0x00, 0x0d, 0x00, 0x00, // .........R...... + 0x00, 0xf7, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ................ + 0x00, 0x51, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0xd5, 0x00, 0x00, 0x00, 0xcf, 0x00, 0x00, // .Q.............. + 0x00, 0x02, 0x00, 0x00, 0x00, 0x52, 0x00, 0x06, 0x00, 0x0d, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x00, // .....R.......... + 0x00, 0xd5, 0x00, 0x00, 0x00, 0xf7, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xf9, 0x00, 0x02, // ................ + 0x00, 0xd6, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0xd6, 0x00, 0x00, 0x00, 0xf5, 0x00, 0x07, // ................ + 0x00, 0x0d, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, // ................ + 0x00, 0xf9, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x96, 0x00, 0x00, // .........>...... + 0x00, 0xfb, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, // .........8...... + 0x00, // . +}; diff --git a/Polyfills/Canvas/shaderc.cmake b/Polyfills/Canvas/shaderc.cmake index c1ea8e1a5..852e3f56e 100644 --- a/Polyfills/Canvas/shaderc.cmake +++ b/Polyfills/Canvas/shaderc.cmake @@ -243,7 +243,7 @@ function(add_bgfx_shader FILE FOLDER) # glsl set(GLSL_OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/Source/Shaders/glsl/${FILENAME}.h) if(NOT "${TYPE}" STREQUAL "COMPUTE") - _bn_shaderc_parse(GLSL ${COMMON} LINUX PROFILE 140 OUTPUT ${GLSL_OUTPUT} BIN2C "${FILENAME}_glsl") + _bn_shaderc_parse(GLSL ${COMMON} LINUX PROFILE 330 OUTPUT ${GLSL_OUTPUT} BIN2C "${FILENAME}_glsl") else() _bn_shaderc_parse(GLSL ${COMMON} LINUX PROFILE 430 OUTPUT ${GLSL_OUTPUT} BIN2C "${FILENAME}_glsl") endif() From 4c6103799ff62901467eabd6d521c7f201bde6d9 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Tue, 29 Sep 2026 16:53:47 -0700 Subject: [PATCH 14/16] Port validation execution and browser environment support Supply the globals, image/font setup, synthetic pointer handling, and engine options needed to run browser Playground scenes in Native. Match browser capture composition and sample settings, reset per-test state, and wait for GUI/utility-scene convergence before capture. Wire bootstrap loading and headless behavior without changing reference images or catalog budgets. Review-Group: H5a Source-Shotgun: d2727b4f61d283db0ed1d92c513065126ed3055a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae Split-From: 56f09c7f26caf65db33d22e845f6b08d0f7c58e8 Split-Part: code --- Apps/Playground/CMakeLists.txt | 9 + Apps/Playground/Scripts/native_env.js | 11 + Apps/Playground/Scripts/validation_native.js | 501 +++++++++++++----- Apps/Playground/Shared/CommandLine.cpp | 3 +- Apps/Playground/Shared/PlaygroundScripts.cpp | 5 +- Apps/Playground/Win32/App.cpp | 2 +- .../JavaScript/src/tests.javaScript.all.ts | 6 +- Apps/package-lock.json | 16 + Apps/package.json | 1 + CMakeLists.txt | 9 +- Documentation/Playground.md | 9 + Plugins/NativeDraco/README.md | 2 + 12 files changed, 429 insertions(+), 145 deletions(-) create mode 100644 Apps/Playground/Scripts/native_env.js diff --git a/Apps/Playground/CMakeLists.txt b/Apps/Playground/CMakeLists.txt index 643de62fd..accc32fba 100644 --- a/Apps/Playground/CMakeLists.txt +++ b/Apps/Playground/CMakeLists.txt @@ -1,6 +1,8 @@ FILE(GLOB REFERENCE_IMAGES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "ReferenceImages/*") set(BABYLON_SCRIPTS + "../node_modules/@babylonjs/havok/lib/umd/HavokPhysics_umd.js" + "../node_modules/@babylonjs/havok/lib/umd/HavokPhysics.wasm" "../node_modules/babylonjs-addons/babylonjs.addons.js" "../node_modules/babylonjs-loaders/babylonjs.loaders.js" "../node_modules/babylonjs/babylon.max.js" @@ -11,11 +13,18 @@ set(BABYLON_SCRIPTS "../node_modules/earcut/dist/earcut.min.js") set(DEPENDENCIES + "../Dependencies/Arimo-Regular.ttf" + "../Dependencies/Arimo-OFL.txt" + "../Dependencies/Arimo-PROVENANCE.txt" + "../Dependencies/DroidSans.ttf" + "../Dependencies/DroidSans-APACHE-2.0.txt" + "../Dependencies/DroidSans-PROVENANCE.txt" "../Dependencies/ammo.js" "../Dependencies/meshwriter.min.js" "../Dependencies/recast.js") set(SCRIPTS + "Scripts/native_env.js" "Scripts/experience.js" "Scripts/playground_runner.js" "Scripts/validation_native.js" diff --git a/Apps/Playground/Scripts/native_env.js b/Apps/Playground/Scripts/native_env.js new file mode 100644 index 000000000..806b2cd7e --- /dev/null +++ b/Apps/Playground/Scripts/native_env.js @@ -0,0 +1,11 @@ +// Browser globals needed before loading Babylon UMD bundles. + +// Let UMD bundles merge into the global object without enabling DOM paths through window. +if (typeof globalThis.global === "undefined") { + globalThis.global = globalThis; +} + +// Provide the browser's default window.name for snippets. +if (typeof globalThis.name === "undefined") { + globalThis.name = ""; +} diff --git a/Apps/Playground/Scripts/validation_native.js b/Apps/Playground/Scripts/validation_native.js index 7ebed839f..4e6b3ac9c 100644 --- a/Apps/Playground/Scripts/validation_native.js +++ b/Apps/Playground/Scripts/validation_native.js @@ -1,3 +1,32 @@ +// Non-enumerable ES2019 polyfills for Chakra. +(function () { + function define(proto, name, fn) { + if (!proto[name]) { + Object.defineProperty(proto, name, { value: fn, writable: true, configurable: true, enumerable: false }); + } + } + define(String.prototype, "trimStart", function () { return this.replace(/^[\s\uFEFF\xA0]+/, ""); }); + define(String.prototype, "trimEnd", function () { return this.replace(/[\s\uFEFF\xA0]+$/, ""); }); + define(Array.prototype, "flat", function (depth) { + var d = depth === undefined ? 1 : Math.floor(depth); + if (isNaN(d) || d < 1) { return Array.prototype.slice.call(this); } + return Array.prototype.reduce.call(this, function (acc, cur) { + if (Array.isArray(cur)) { + const values = cur.flat(d - 1); + for (let i = 0; i < values.length; ++i) { + acc.push(values[i]); + } + } else { + acc.push(cur); + } + return acc; + }, []); + }); + define(Array.prototype, "flatMap", function (cb, thisArg) { + return Array.prototype.map.call(this, cb, thisArg).flat(); + }); +})(); + (function () { let currentScene; let config; @@ -6,6 +35,8 @@ const saveResult = (typeof opts.saveResults === "boolean") ? opts.saveResults : true; const testWidth = 600; const testHeight = 400; + // Browser visualization tests create their engine with antialias=false. + TestUtils.setMSAASamples(0); const generateReferences = !!opts.generateReferences; const breakOnFail = !!opts.breakOnFail; const stopOnFirstFailure = !!opts.stopOnFirstFailure; @@ -13,9 +44,7 @@ const includeExcluded = !!opts.includeExcluded; const testFilters = Array.isArray(opts.testFilters) ? opts.testFilters.map(s => String(s).toLowerCase()) : []; const testIndices = Array.isArray(opts.testIndices) ? opts.testIndices.map(n => +n) : []; - // CLI --capture=N: 1-based frame index at which to call - // TestUtils.captureNextFrame() for every executed test. The runner - // extends each test's render budget so the .rdc finalizes. + // One-based capture frame; extend rendering to let RenderDoc finalize. const cliCaptureFrame = (typeof opts.captureFrame === "number" && opts.captureFrame > 0) ? (opts.captureFrame | 0) : 0; // Frames after the trigger to let RenderDoc finalize the .rdc. const POST_CAPTURE_FRAMES = 5; @@ -27,6 +56,11 @@ const INITIAL_READINESS_TIMEOUT_MS = 10 * 60 * 1000; const READINESS_RECONCILE_INTERVAL_MS = 100; const utilityLayerOwners = new WeakMap(); + const dracoDecoderConfiguration = Object.assign({}, BABYLON.DracoDecoder.DefaultConfiguration); + const dracoEncoderConfiguration = Object.assign({}, BABYLON.DracoEncoder.DefaultConfiguration); + const dracoDefaultNumWorkers = BABYLON.DracoCompression.DefaultNumWorkers; + const dracoDecoderModule = globalThis.DracoDecoderModule; + const dracoEncoderModule = globalThis.DracoEncoderModule; // UtilityLayerRenderer exposes both sides of the association, but its utility // scene can have no active camera when the layer is created before the main camera. @@ -36,6 +70,30 @@ utilityLayerOwners.set(this.utilityLayerScene, this.originalScene); return result; }; + let havokInitializationPromise; + + function initializeHavokAsync() { + if (typeof HK !== "undefined") { + return Promise.resolve(); + } + + if (!havokInitializationPromise) { + havokInitializationPromise = Promise.all([ + BABYLON.Tools.LoadFileAsync("app:///Scripts/HavokPhysics_umd.js", false), + BABYLON.Tools.LoadFileAsync("app:///Scripts/HavokPhysics.wasm", true) + ]).then(function (sources) { + (0, eval)(sources[0]); + if (typeof HavokPhysics !== "function") { + throw new Error("HavokPhysics_umd.js did not register HavokPhysics"); + } + return HavokPhysics({ wasmBinary: new Uint8Array(sources[1]) }); + }).then(function (instance) { + globalThis.HK = instance; + }); + } + + return havokInitializationPromise; + } function shouldRunTest(test, index) { if (testIndices.length > 0 && testIndices.indexOf(index) === -1) { @@ -54,28 +112,21 @@ } function failTest(done) { - // done is the once-only completion wrapper from runTest; it stops the - // render loop, runs inter-test cleanup, then advances the suite. if (breakOnFail) { - // Trigger the JS debugger if attached; on no-debugger runs the - // host's bx exception filter prints a callstack on the next throw. // eslint-disable-next-line no-debugger debugger; } done(false); } - // Dispose the current/stray scenes and reset engine state so the next test - // starts clean. Safe to call more than once (guards on currentScene). + // Reset the reused engine between tests. function cleanupAfterTest() { if (currentScene) { try { currentScene.dispose(); } catch (e) { console.error(e); } currentScene = null; } - // A test can leave extra scenes behind (an async load that created its own scene, a scene - // whose creation promise resolved after validation, ...). They stay registered on the - // reused engine and keep their resources alive, so dispose them here. + // Async loads may leave additional scenes registered on the engine. if (engine && engine.scenes) { const strayScenes = engine.scenes.slice(); for (let i = 0; i < strayScenes.length; ++i) { @@ -89,9 +140,6 @@ engine.setHardwareScalingLevel(1); - // Reset render state that persists on the reused engine so each test starts fresh. - // A test that leaves the stencil test enabled or a scissor rect set would otherwise - // corrupt later tests (e.g. the glow-layer test). engine.setStencilBuffer(false); engine.disableScissor(); engine.useReverseDepthBuffer = false; @@ -100,31 +148,28 @@ // This is necessary because of https://github.com/BabylonJS/Babylon.js/pull/15217 so that each test starts fresh. engine.releaseEffects(); - // Textures are cached on the engine by URL (BaseTexture._getFromCache), and the cache key - // covers only url/noMipmap/isCube -- not the load-time options. A test that leaves a - // reference behind (e.g. assigning one texture to both scene.environmentTexture and a - // material's reflectionTexture) keeps its internal texture in that cache across - // scene.dispose(), so a later test loading the same URL silently reuses the *previous* - // test's texture along with its prefiltering/irradiance settings. Release whatever is - // left so every test loads its own textures and results do not depend on run order. + // Cache keys omit load-time options; leaked textures can change later tests. const leakedTextures = engine.getLoadedTexturesCache(); for (let i = leakedTextures.length - 1; i >= 0; --i) { engine._releaseTexture(leakedTextures[i]); } engine.clearInternalTexturesCache(); - // SceneLoader.OnPluginActivatedObservable is global and outlives the scene. Snippets use it - // to configure the glTF loader (animationStartMode, compileMaterials, ...) and never - // unregister, so without this every later glTF test would inherit those settings. The - // browser harness reloads the page per test and never sees this; here the engine is reused. + // Global loader observers outlive scenes and can leak settings into later tests. BABYLON.SceneLoader.OnPluginActivatedObservable.clear(); + + // Each scene must load matching Draco JS/WASM modules, not reuse another scene's factory. + BABYLON.DracoCompression.ResetDefault(); + BABYLON.DracoDecoder.ResetDefault(); + BABYLON.DracoEncoder.ResetDefault(); + BABYLON.DracoCompression.DefaultNumWorkers = dracoDefaultNumWorkers; + BABYLON.DracoDecoder.DefaultConfiguration = Object.assign({}, dracoDecoderConfiguration); + BABYLON.DracoEncoder.DefaultConfiguration = Object.assign({}, dracoEncoderConfiguration); + globalThis.DracoDecoderModule = dracoDecoderModule; + globalThis.DracoEncoderModule = dracoEncoderModule; } - // Wrap the recursiveRunTest callback so every completion path (pixel pass/fail, - // render-loop throw, onReadyTimeout, load/eval errors) stops the loop, cleans once, - // and only then schedules the next test. Babylon runRenderLoop appends callbacks; - // without this, a failing test can leave a stale callback that renders the next - // scene or calls done(false) repeatedly under continue-on-failure. + // Every completion path must stop rendering and clean up exactly once. function makeTestDone(outerDone) { let finished = false; return function (status) { @@ -133,7 +178,9 @@ } finished = true; try { - engine.stopRenderLoop(); + if (engine) { + engine.stopRenderLoop(); + } } catch (e) { console.error(e); } @@ -142,13 +189,6 @@ }; } - // Emitted after a pixel-comparison failure to make triage faster. Prints the - // rendered/diff PNG paths plus a re-run command. For scenes fetched from the - // snippet server it also notes that assets/fonts arrive over the network, so - // async load timing is one possible cause. A stable pixel-difference count on - // re-runs is a reason to open the saved result/diff images -- not proof that - // timing has been ruled out (the count discards which pixels changed and by - // how much, and a repeatable timing failure can produce the same fallback). function logFailureDiagnostics(test) { const outDir = TestUtils.getOutputDirectory(); if (test.referenceImage) { @@ -192,10 +232,10 @@ function logRunSummary() { console.log("Run complete. ran=" + ranCount + - " passed=" + passedCount + - " failed=" + failedCount + - " missingRef=" + missingRefCount + - " skipped=" + skippedCount); + " passed=" + passedCount + + " failed=" + failedCount + + " missingRef=" + missingRefCount + + " skipped=" + skippedCount); if (failedTitles.length > 0) { console.log("Failed tests (" + failedTitles.length + "):"); for (let n = 0; n < failedTitles.length; n++) { @@ -204,15 +244,32 @@ } } - const engine = new BABYLON.NativeEngine(); - globalThis.engine = engine; - engine.getCaps().parallelShaderCompile = undefined; + let engine; + let useHighPrecisionMatrices = false; - // Broaden Babylon's default retry strategy for the test framework: in addition to - // network drops (status 0, the default trigger), also retry transient HTTP errors - // (5xx) and rate limits (429). Applies to every BABYLON.Tools.LoadFile request - // including the snippet fetches in loadPG below and the texture/asset loads - // initiated from inside each playground's createScene(). + function createEngine(useLargeWorldRendering) { + const nativeEngine = new BABYLON.NativeEngine({ + useLargeWorldRendering, + useHighPrecisionMatrix: useHighPrecisionMatrices + }); + nativeEngine.getCaps().parallelShaderCompile = undefined; + nativeEngine.getRenderingCanvas = function () { return window; }; + nativeEngine.getInputElement = function () { return 0; }; + if (!window.screen) { + window.screen = { + width: nativeEngine.getRenderWidth(), + height: nativeEngine.getRenderHeight(), + availWidth: nativeEngine.getRenderWidth(), + availHeight: nativeEngine.getRenderHeight(), + colorDepth: 24, + pixelDepth: 24, + orientation: { angle: 0, type: "landscape-primary" } + }; + } + return nativeEngine; + } + + // Retry network failures, transient server errors, and rate limits for all scene assets. BABYLON.Tools.DefaultRetryStrategy = function (url, request, retryIndex) { const maxRetries = 5; if (retryIndex >= maxRetries) { @@ -229,13 +286,82 @@ return -1; }; - engine.getRenderingCanvas = function () { - return window; + // Preserve the window/canvas identity used by input handling. + if (!window.style) { + window.style = {}; } - - engine.getInputElement = function () { - return 0; + if (typeof window.focus !== "function") { + window.focus = function () { }; } + if (typeof window.blur !== "function") { + window.blur = function () { }; + } + // Dispatch through InputManager's picking paths; simulatePointer* bypasses skipPointer*Picking. + const POINTER_INPUT_MOVE = 12; + const domListeners = new Map(); + window.addEventListener = function (type, listener) { + if (typeof listener !== "function") { + return; + } + if (!domListeners.has(type)) { + domListeners.set(type, []); + } + domListeners.get(type).push(listener); + }; + window.removeEventListener = function (type, listener) { + const list = domListeners.get(type); + if (list) { + const at = list.indexOf(listener); + if (at !== -1) { + list.splice(at, 1); + } + } + }; + window.dispatchEvent = function (evt) { + if (!evt) { + return true; + } + + if (evt.target === null || evt.target === undefined) { + evt.target = window; + } + + const list = domListeners.get(evt.type); + if (list) { + for (const listener of list.slice()) { + listener.call(window, evt); + } + } + + const inputManager = currentScene && currentScene._inputManager; + if (inputManager) { + if (evt.button === undefined) { + evt.button = 0; + } + switch (evt.type) { + case "pointermove": + evt.inputIndex = POINTER_INPUT_MOVE; + inputManager._onPointerMove(evt); + break; + case "pointerdown": + evt.inputIndex = evt.button + 2; + inputManager._onPointerDown(evt); + break; + case "pointerup": + evt.inputIndex = evt.button + 2; + inputManager._onPointerUp(evt); + break; + case "keydown": + inputManager._onKeyDown(evt); + break; + case "keyup": + inputManager._onKeyUp(evt); + break; + } + } + + return !evt.defaultPrevented; + }; const canvas = window; globalThis.canvas = canvas; @@ -305,11 +431,44 @@ return false; // no error } + // Match browser screenshots: composite over the CSS canvas background, then the white page. + // FrameGraph may copy transparent attachments regardless of scene.clearColor. + // Legacy paths retain the clear-alpha gate because some write invalid alpha after opaque clears. + const CANVAS_BACKGROUND = [173, 255, 47]; + + function compositeOverCanvasBackground(data, canvasBackgroundColor) { + let background = CANVAS_BACKGROUND; + if (canvasBackgroundColor) { + const rgba = _native.Canvas.parseColor(canvasBackgroundColor); + const alpha = (rgba >>> 24) / 255; + background = [rgba & 255, (rgba >>> 8) & 255, (rgba >>> 16) & 255] + .map(channel => channel * alpha + 255 * (1 - alpha)); + } + for (let index = 0; index < data.length; index += 4) { + const alpha = data[index + 3]; + if (alpha === 255) { + continue; + } + const src = alpha / 255; + const dst = 1 - src; + data[index] = Math.round(data[index] * src + background[0] * dst); + data[index + 1] = Math.round(data[index + 1] * src + background[1] * dst); + data[index + 2] = Math.round(data[index + 2] * src + background[2] * dst); + data[index + 3] = 255; + } + return data; + } + function evaluateScreenshot(test, screenshot, referenceImage, done, compareFunction) { let testRes = true; + if (test.canvasBackgroundColor || (currentScene && (currentScene.frameGraph || (currentScene.clearColor && currentScene.clearColor.a < 1)))) { + compositeOverCanvasBackground(screenshot, test.canvasBackgroundColor); + } + if (!test.onlyVisual) { + // Historical baseline; lower per-test exceptions as rendering fixes reach this target. const defaultErrorRatio = 2.5; if (compareFunction(test, screenshot, referenceImage, test.threshold || 25, test.errorRatio || defaultErrorRatio)) { @@ -335,6 +494,12 @@ }); } + function areGuiTexturesReady(scene) { + return scene.textures.every(function (texture) { + return typeof texture.guiIsReady !== "function" || texture.guiIsReady(); + }); + } + function isSceneConverged(scene) { const engine = scene.getEngine(); const previousRenderPassId = engine.currentRenderPassId; @@ -342,11 +507,8 @@ if (!scene.isReady()) { return false; } - for (let i = 0; i < scene.textures.length; i++) { - const texture = scene.textures[i]; - if (typeof texture.guiIsReady === "function" && !texture.guiIsReady()) { - return false; - } + if (!areGuiTexturesReady(scene)) { + return false; } // Hot-swapping materials may report ready while their replacement effect is @@ -397,19 +559,13 @@ function processCurrentScene(test, renderImage, done, compareFunction) { currentScene.useConstantAnimationDeltaTime = true; - // Frame at which to read back the framebuffer & validate. This is the - // test's renderCount (default 1) and determines pass/fail. NOT shifted - // by --capture. + // Capture options must not shift the pixel-comparison frame. const compareFrame = test.renderCount || 1; - // Frame at which to call TestUtils.captureNextFrame(), or 0 if no - // capture is requested. CLI --capture=N takes precedence over the - // per-test "capture" config flag; the legacy per-test flag triggers - // at compareFrame. + // CLI capture overrides the legacy per-test capture flag. const captureFrame = cliCaptureFrame > 0 ? cliCaptureFrame : (test.capture ? compareFrame : 0); - // Stop after this many frames. With --capture we keep rendering past - // compareFrame so RenderDoc can finalize the .rdc. + // Allow RenderDoc to finalize after the capture frame. const stopFrame = captureFrame > 0 ? Math.max(compareFrame, captureFrame + POST_CAPTURE_FRAMES) : compareFrame; @@ -424,6 +580,8 @@ let readinessReconcileTimer = null; let readinessTimeoutTimer = null; let waitingForReadiness = true; + // Effect-layer RTTs need one submitted frame before composition, even with renderCount=1. + let effectLayerPrimed = false; const runEvaluation = function (screenshot) { if (evaluated) { @@ -491,6 +649,13 @@ } return; } + + if (!effectLayerPrimed && currentScene.effectLayers && currentScene.effectLayers.length > 0) { + effectLayerPrimed = true; + currentScene.render(); + return; + } + frameIndex++; if (captureFrame > 0 && frameIndex === captureFrame && TestUtils.captureNextFrame) { @@ -600,7 +765,8 @@ let allReady = readinessScenes.length > 0; for (let i = 0; i < readinessScenes.length; i++) { - if (readyScenes.indexOf(readinessScenes[i]) === -1) { + // GUI image loads are not included in Scene.executeWhenReady. + if (readyScenes.indexOf(readinessScenes[i]) === -1 || !areGuiTexturesReady(readinessScenes[i])) { allReady = false; break; } @@ -740,12 +906,7 @@ } const pgCode = code + "\r\ncreateScene(engine)"; - // Defer scene construction to a fresh macrotask so - // eval()/createScene() run at a shallow native-stack - // depth instead of nested inside the native snippet - // load callback. Deep scenes otherwise pile onto the - // native XHR dispatch frames and can overflow engines - // with a small C stack (e.g. QuickJS). + // Leave the native XHR callback stack before constructing deep scenes. setTimeout(async function () { if (finished) { return; @@ -753,21 +914,17 @@ // eslint-disable-next-line no-unused-vars var name = ""; // see the note on the scriptToRun eval below try { - // Runs before the first await, so the eval still happens at the - // shallow stack depth this setTimeout exists to provide. + if (test.requiresHavok) { + await initializeHavokAsync(); + if (finished) { + return; + } + } + let createdScene = eval(pgCode); if (createdScene && createdScene.then) { - // Handle if createScene returns a promise. Guard against a - // snippet whose promise never resolves (e.g. a scene whose - // utility-layer executeWhenReady never fires on Native): the - // onReadyTimeout safety net lives inside processCurrentScene - // and only applies AFTER the promise resolves, so without this - // a pending createScene promise hangs the whole suite. Mirror - // onReadyTimeoutDuration and convert it to a fast failure. - // Note: this only fires if the JS event loop keeps running; a - // snippet that blocks the JS thread natively (e.g. manual - // setInterval frame-driving) is not rescued by this. + // Bound async creation before scene readiness begins; native blocking needs an external timeout. const createSceneTimeoutMs = 10 * 60 * 1000; let createSceneTimeoutId; try { @@ -788,12 +945,11 @@ ]); } finally { - // Always clear it: a pending timer would otherwise keep the - // event loop alive for the full timeout after a scene that - // resolved normally. clearTimeout(createSceneTimeoutId); rejectSceneCreation = undefined; } + } else { + currentScene = createdScene; } if (finished) { @@ -867,23 +1023,12 @@ } const scriptCode = scriptToRun + test.functionToCall + "(engine)"; - // Defer scene construction to a fresh macrotask so - // eval()/() run at a shallow native-stack - // depth instead of nested inside the native XHR - // completion callback. Deep scenes otherwise pile onto - // the native XHR dispatch frames and can overflow engines - // with a small C stack (e.g. QuickJS). + // Leave the native XHR callback stack before constructing deep scenes. setTimeout(function () { if (finished) { return; } - // Browser scripts sometimes reference `name` without declaring it. In a - // page that silently resolves to window.name (""), so the mistake is - // invisible there but throws "ReferenceError: name is not defined" - // here. eval() below is a *direct* eval, so the evaluated script sees - // this function's scope and finds this binding -- same as it would on - // the web, without leaking an actual global. (A real global `name` - // is not an option: it breaks the Babylon UMD bundles at load time.) + // Direct eval sees the browser's default name through this local binding. // eslint-disable-next-line no-unused-vars var name = ""; try { @@ -922,19 +1067,33 @@ console.log(testInfo); TestUtils.setTitle(testInfo); + try { + const useLargeWorldRendering = !!test.useLargeWorldRendering; + if (!engine || !!engine.getCreationOptions().useLargeWorldRendering !== useLargeWorldRendering) { + if (engine) { + engine.dispose(); + engine = undefined; + globalThis.engine = undefined; + } + engine = createEngine(useLargeWorldRendering); + globalThis.engine = engine; + } + } catch (e) { + console.error(e); + failTest(done); + return; + } + seed = 1; Math.random = seededRandom; if (generateReferences) { loadPlayground(test, done, undefined, saveRenderedResult); } else { - // Config validation: missing 'referenceImage' field is a permanent - // catalog error (not a runtime asset-missing case), so short-circuit - // before issuing the load. onlyVisual tests skip pixel comparison - // so they don't need the reference image to exist. + // Pixel comparisons require a catalog reference before loading the scene. if (!test.onlyVisual && !test.referenceImage) { console.error("MISSING_REFERENCE_IMAGE: Test '" + (test.title || "(unnamed)") + - "' has no 'referenceImage' field in config.json - cannot run pixel comparison."); + "' has no 'referenceImage' field in config.json - cannot run pixel comparison."); missingRefCount++; failTest(done); return; @@ -944,13 +1103,10 @@ const url = "app:///ReferenceImages/" + test.referenceImage; const onLoadFileError = function (request, exception) { - // Reference-image load failures (missing file on disk, etc.) - // arrive here via JsRuntimeHost's XHR error event + - // BABYLON.Tools.LoadFile's onLoadFileError callback. Tag with - // MISSING_REFERENCE_IMAGE: so CI greps still match. + // Keep the diagnostic tag used by CI. console.error("MISSING_REFERENCE_IMAGE: Test '" + (test.title || "(unnamed)") + - "' failed to load reference at " + url + ". " + - (exception ? exception : "(no exception details)")); + "' failed to load reference at " + url + ". " + + (exception ? exception : "(no exception details)")); missingRefCount++; failTest(done); }; @@ -982,10 +1138,61 @@ }; } + // Expose the native Canvas Image under its DOM name. + if (typeof globalThis.Image === "undefined" && typeof _native !== "undefined" && _native.Image) { + globalThis.Image = _native.Image; + } + + if (typeof globalThis.KeyboardEvent === "undefined") { + // InputManager only needs keyboard-event state, not a DOM event implementation. + globalThis.KeyboardEvent = function (type, init) { + this.type = type; + for (const key in (init || {})) { + this[key] = init[key]; + } + if (this.key === undefined) { this.key = ""; } + if (this.code === undefined) { this.code = ""; } + if (this.keyCode === undefined) { this.keyCode = 0; } + if (this.ctrlKey === undefined) { this.ctrlKey = false; } + if (this.altKey === undefined) { this.altKey = false; } + if (this.shiftKey === undefined) { this.shiftKey = false; } + if (this.metaKey === undefined) { this.metaKey = false; } + if (this.repeat === undefined) { this.repeat = false; } + this.target = null; + this.defaultPrevented = false; + this.preventDefault = function () { this.defaultPrevented = true; }; + this.stopPropagation = function () { }; + this.stopImmediatePropagation = function () { }; + }; + } + + // InputManager only needs pointer-event state, not a DOM event implementation. + if (typeof globalThis.PointerEvent === "undefined") { + globalThis.PointerEvent = function (type, init) { + this.type = type; + for (const key in (init || {})) { + this[key] = init[key]; + } + if (this.pointerId === undefined) { this.pointerId = 1; } + if (this.pointerType === undefined) { this.pointerType = "mouse"; } + if (this.button === undefined) { this.button = 0; } + if (this.buttons === undefined) { this.buttons = 0; } + if (this.clientX === undefined) { this.clientX = 0; } + if (this.clientY === undefined) { this.clientY = 0; } + if (this.movementX === undefined) { this.movementX = 0; } + if (this.movementY === undefined) { this.movementY = 0; } + this.target = null; + this.defaultPrevented = false; + this.preventDefault = function () { this.defaultPrevented = true; }; + this.stopPropagation = function () { }; + }; + } + document = { createElement: function (type) { if (type === "canvas") { - return new OffscreenCanvas(64, 64); + // Image-processing snippets need a real 2D drawing/readback context. + return engine.createCanvas(64, 64); } return {}; }, @@ -1000,29 +1207,24 @@ config = JSON.parse(xhr.responseText); if (listTests) { - // Canonical TSV: indextitlereferenceImageexclusionReason. - // exclusionReason reflects config state (ignores --include-excluded) - // so the listing is the same regardless of run flags. + // TSV exclusions reflect the catalog, regardless of --include-excluded. for (let i = 0; i < config.tests.length; ++i) { const t = config.tests[i]; const reason = getExclusionReason(t) || ""; console.log(i + "\t" + (t.title || "") + "\t" + (t.referenceImage || "") + "\t" + reason); } - engine.dispose(); TestUtils.exit(0); return; } + const runnableTests = config.tests.filter((test, index) => shouldRunTest(test, index) && getSkipReason(test) === null); + const selectedTests = justOnce ? runnableTests.slice(0, 1) : runnableTests; + // Precision is global, and cached matrices can only be promoted before the first engine. + useHighPrecisionMatrices = selectedTests.some(test => test.useLargeWorldRendering || test.useHighPrecisionMatrix); + // Run tests const recursiveRunTest = function (i) { - // Skip filtered-out tests cheaply (don't count toward --once - // and don't re-init the engine). - // - // Skipped tests (excludeFromAutomaticTesting / onlyVisual / - // excludedGraphicsApis) are logged loudly when a filter is - // active so the user sees that --test "X" matched but was - // skipped. Filter mismatches stay silent to avoid noise on - // unfiltered runs. + // Filter mismatches stay silent and do not consume --once. while (i < config.tests.length) { const t = config.tests[i]; const matchesFilter = shouldRunTest(t, i); @@ -1041,7 +1243,9 @@ } if (i >= config.tests.length) { logRunSummary(); - engine.dispose(); + if (engine) { + engine.dispose(); + } TestUtils.exit(failedCount > 0 ? -1 : 0); return; } @@ -1064,7 +1268,9 @@ i++; if (justOnce || i >= config.tests.length) { logRunSummary(); - engine.dispose(); + if (engine) { + engine.dispose(); + } TestUtils.exit(failedCount > 0 ? -1 : 0); return; } @@ -1079,13 +1285,30 @@ }, false); - BABYLON.Tools.LoadFile("https://raw.githubusercontent.com/CedricGuillemet/dump/master/droidsans.ttf", (data) => { - _native.Canvas.loadTTFAsync("droidsans", data).then(function () { - _native.RootUrl = "https://playground.babylonjs.com"; - console.log("Starting"); - TestUtils.setTitle("Starting Native Validation Tests"); - TestUtils.updateSize(testWidth, testHeight); - xhr.send(); + function loadFontAssetAsync(url) { + return BABYLON.Tools.LoadFileAsync(url, true).then(function (data) { + if (!(data instanceof ArrayBuffer) || data.byteLength === 0) { + throw new Error("Invalid font response from " + url); + } + return data; }); - }, undefined, undefined, true); + } + + Promise.all([ + loadFontAssetAsync("app:///Scripts/DroidSans.ttf"), + loadFontAssetAsync("app:///Scripts/Arimo-Regular.ttf") + ]).then(function (fonts) { + _native.Canvas.loadTTF("droidsans", fonts[0]); + _native.Canvas.loadTTF("monospace", fonts[0]); + return _native.Canvas.loadTTFAsync("Arial", fonts[1]); + }).then(function () { + _native.RootUrl = "https://playground.babylonjs.com"; + console.log("Starting"); + TestUtils.setTitle("Starting Native Validation Tests"); + TestUtils.updateSize(testWidth, testHeight); + xhr.send(); + }).catch(function (error) { + console.error("Failed to initialize validation fonts: " + error); + TestUtils.exit(1); + }); })(); diff --git a/Apps/Playground/Shared/CommandLine.cpp b/Apps/Playground/Shared/CommandLine.cpp index b2363f61f..ac077df7f 100644 --- a/Apps/Playground/Shared/CommandLine.cpp +++ b/Apps/Playground/Shared/CommandLine.cpp @@ -146,7 +146,8 @@ namespace [](PlaygroundOptions& o, std::string_view, std::string&) { o.ListTests = true; }}, FlagSpec{"--headless", "", FlagKind::Boolean, "", - "Don't show a window (still creates HWND).", "", + "Don't show a window (still creates HWND).", + " Disables the JavaScript debugger listener.\n", [](PlaygroundOptions& o, std::string_view, std::string&) { o.Headless = true; }}, FlagSpec{"--break-on-fail", "", FlagKind::Boolean, "", diff --git a/Apps/Playground/Shared/PlaygroundScripts.cpp b/Apps/Playground/Shared/PlaygroundScripts.cpp index 027218cfb..771bef7e6 100644 --- a/Apps/Playground/Shared/PlaygroundScripts.cpp +++ b/Apps/Playground/Shared/PlaygroundScripts.cpp @@ -32,6 +32,8 @@ namespace Playground void LoadBootstrapScripts(Babylon::Embedding::Runtime& runtime) { + // UMD bundles require these globals before loading. + runtime.LoadScript("app:///Scripts/native_env.js"); runtime.LoadScript("app:///Scripts/ammo.js"); // Commenting out recast.js for now because v8jsi is incompatible with asm.js. // runtime.LoadScript("app:///Scripts/recast.js"); @@ -44,10 +46,11 @@ namespace Playground runtime.LoadScript("app:///Scripts/babylonjs.addons.js"); runtime.LoadScript("app:///Scripts/babylonjs.loaders.js"); runtime.LoadScript("app:///Scripts/babylonjs.materials.js"); + // Procedural-textures library (Fire/Wood/Marble/... ProceduralTexture). + runtime.LoadScript("app:///Scripts/babylonjs.proceduralTextures.js"); runtime.LoadScript("app:///Scripts/babylon.gui.js"); runtime.LoadScript("app:///Scripts/meshwriter.min.js"); runtime.LoadScript("app:///Scripts/babylonjs.serializers.js"); - runtime.LoadScript("app:///Scripts/babylonjs.proceduralTextures.js"); } std::function diff --git a/Apps/Playground/Win32/App.cpp b/Apps/Playground/Win32/App.cpp index ddba9e278..4b0b4f39d 100644 --- a/Apps/Playground/Win32/App.cpp +++ b/Apps/Playground/Win32/App.cpp @@ -93,7 +93,7 @@ namespace Babylon::Embedding::RuntimeOptions MakeRuntimeOptions() { Babylon::Embedding::RuntimeOptions runtimeOptions{}; - runtimeOptions.enableDebugger = true; + runtimeOptions.enableDebugger = !options.Headless; runtimeOptions.enableDebugTrace = options.DebugTrace.value_or(true); runtimeOptions.log = Playground::MakeLogCallback([](std::string_view text) { std::string line{text}; diff --git a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts index cbe638a28..cefcb3751 100644 --- a/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts +++ b/Apps/UnitTests/JavaScript/src/tests.javaScript.all.ts @@ -1101,13 +1101,15 @@ describe("Canvas2D", function () { expect(function () { ctx.fill(impostor); }).to.throw(); expect(function () { ctx.stroke(impostor); }).to.throw(); expect(function () { realPath.addPath(impostor); }).to.throw(); - // Non-Path2D arguments use the DOMString overload. + // The Path2D() argument is a (Path2D or DOMString) union, so a non-Path2D is string + // data rather than an error. It must not be unwrapped on the way there. expect(function () { new Path2D(impostor); }).to.not.throw(); } }); it("ignores a prototype-spoofed object assigned to fillStyle or strokeStyle", function () { - // A forged gradient prototype must not authorize native unwrapping. + // Same defect on the gradient side: the assignment gate accepted anything wearing the + // gradient prototype and stored it, and the next fill unwrapped it as a CanvasGradient. const ctx = createContext(); const gradient = ctx.createLinearGradient(0, 0, 10, 10); const spoofedPath: any = new Path2D(); diff --git a/Apps/package-lock.json b/Apps/package-lock.json index 070d885cf..48e0a54f1 100644 --- a/Apps/package-lock.json +++ b/Apps/package-lock.json @@ -12,6 +12,7 @@ "UnitTests/JavaScript" ], "dependencies": { + "@babylonjs/havok": "1.3.12", "babylonjs": "^9.29.0", "babylonjs-addons": "^9.29.0", "babylonjs-gltf2interface": "^9.29.0", @@ -1933,6 +1934,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@babylonjs/havok": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.3.12.tgz", + "integrity": "sha512-KR5Z7DBkVEgdvHLMDh2VWe/nHvUG8+MdLBiAE0iM19KIHAPqPRVITPAZKx4SQusK5nqm4ZXDcKv5OYtViIxLzA==", + "license": "MIT", + "dependencies": { + "@types/emscripten": "^1.39.6" + } + }, "node_modules/@babylonjs/materials": { "version": "9.29.0", "resolved": "https://registry.npmjs.org/@babylonjs/materials/-/materials-9.29.0.tgz", @@ -2128,6 +2138,12 @@ "node": ">=14" } }, + "node_modules/@types/emscripten": { + "version": "1.41.6", + "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.6.tgz", + "integrity": "sha512-uN+9i8bFT5CUcZfyIEYDrSueACEyKGbUs5kC/72DGlZZoinh84sJfVV0i8UOJD1asdzkvLPBRrKs41kZ8MdEXg==", + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", diff --git a/Apps/package.json b/Apps/package.json index 995f58dda..5645cb2e6 100644 --- a/Apps/package.json +++ b/Apps/package.json @@ -11,6 +11,7 @@ "postinstall": "node scripts/downlevelNativeScripts.mjs node_modules" }, "dependencies": { + "@babylonjs/havok": "1.3.12", "babylonjs": "^9.29.0", "babylonjs-addons": "^9.29.0", "babylonjs-gltf2interface": "^9.29.0", diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fe610ed..ce8851fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,10 +45,17 @@ FetchContent_Declare(glslang GIT_REPOSITORY https://github.com/BabylonJS/glslang.git GIT_TAG 284e4301e5a6b44b279635276588db7cdd942624 EXCLUDE_FROM_ALL) +# The codec does not use the transcoder's external dependencies. +set(_draco_submodules "") +if(DRACO_TRANSCODER_SUPPORTED) + set(_draco_submodules third_party/eigen third_party/filesystem third_party/tinygltf) +endif() FetchContent_Declare(draco GIT_REPOSITORY https://github.com/google/draco.git GIT_TAG 1.5.7 + GIT_SUBMODULES "${_draco_submodules}" EXCLUDE_FROM_ALL) +unset(_draco_submodules) FetchContent_Declare(meshoptimizer GIT_REPOSITORY https://github.com/zeux/meshoptimizer.git GIT_TAG v0.22 @@ -62,7 +69,7 @@ FetchContent_Declare(ios-cmake EXCLUDE_FROM_ALL) FetchContent_Declare(JsRuntimeHost GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git - GIT_TAG 33e4a233dea178712352ec90a647683084bfbf1d) + GIT_TAG 2f8b9be3a4d691d4a76c85c9c856319eb25f744c) FetchContent_Declare(metal-cpp GIT_REPOSITORY https://github.com/bkaradzic/metal-cpp.git GIT_TAG metal-cpp_26 diff --git a/Documentation/Playground.md b/Documentation/Playground.md index 35b18023c..3fe91f999 100644 --- a/Documentation/Playground.md +++ b/Documentation/Playground.md @@ -32,6 +32,15 @@ file. Essentially, this allows for externally-defined JS files to control the behavior of the Playground app as an alternative to experience.js -- at runtime and without the need to recompile or redeploy. +On Win32, `--headless` also disables the JavaScript debugger listener to avoid +firewall prompts during automated runs. Interactive debugging is unchanged. +Validation packages its licensed fonts and Havok runtime; tests marked +`requiresHavok` initialize Havok before scene creation. +Between scenes, validation resets engine state and Draco configuration/modules +so tests can switch codecs without inheriting an incompatible decoder. +GUI image loading shares the initial scene-readiness deadline; it does not +consume shader-convergence ticks or advance the comparison frame. + The Playground app also features a "hot reload" capability on certain platforms (Win32 and UWP, for now). The current implementation is that pressing a button (`R` at present, likely to be remapped to `F5` in the diff --git a/Plugins/NativeDraco/README.md b/Plugins/NativeDraco/README.md index 084ea2084..973f59076 100644 --- a/Plugins/NativeDraco/README.md +++ b/Plugins/NativeDraco/README.md @@ -6,6 +6,8 @@ The NativeDraco plugin provides native [Draco](https://github.com/google/draco) The plugin is **off by default**. Enable it with `-D BABYLON_NATIVE_PLUGIN_NATIVEDRACO=ON`. +Codec-only builds skip Draco's unused third-party submodules. Setting `DRACO_TRANSCODER_SUPPORTED=ON` also fetches the transcoder's Eigen, filesystem, and tinygltf dependencies. + ## Limitations - **glTF bitstream subset.** Draco is built with `DRACO_GLTF_BITSTREAM=ON`. NativeDraco is intended to produce and consume glTF-compatible Draco data (Babylon.js defaults to the glTF-only decoder). The subset still supports mesh encoding, normals, and standard Edgebreaker; it constrains the output rather than disabling encoding, and avoids features outside the glTF profile (e.g. predictive valence at slower speeds) that the default decoder may reject. Attribute deduplication may be compiled out of the subset; `Encode` guards those passes on the feature macros Draco publishes. From 6401e9188bbe89571fd47943f0e596adf9a32dbc Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Mon, 5 Oct 2026 20:37:35 -0700 Subject: [PATCH 15/16] Regenerate the combined JavaScript unit-test bundle Build tests.javaScript.all.js once for the retained protocol-9 regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae --- .../JavaScript/dist/tests.javaScript.all.js | 1597 +++++++++++++---- 1 file changed, 1270 insertions(+), 327 deletions(-) diff --git a/Apps/UnitTests/JavaScript/dist/tests.javaScript.all.js b/Apps/UnitTests/JavaScript/dist/tests.javaScript.all.js index a5d9cda5b..24de5e6cf 100644 --- a/Apps/UnitTests/JavaScript/dist/tests.javaScript.all.js +++ b/Apps/UnitTests/JavaScript/dist/tests.javaScript.all.js @@ -1,6 +1,278 @@ /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ +/***/ "./src/tests.nativeEngine.attributeLessInstancing.ts" +/*!***********************************************************!*\ + !*** ./src/tests.nativeEngine.attributeLessInstancing.ts ***! + \***********************************************************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ registerAttributeLessInstancingTests: () => (/* binding */ registerAttributeLessInstancingTests) +/* harmony export */ }); +/* harmony import */ var _babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/asyncToGenerator */ "../../node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js"); +/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/regenerator */ "../../node_modules/@babel/runtime/regenerator/index.js"); +/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var chai__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! chai */ "../../node_modules/chai/index.js"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babylonjs/core */ "@babylonjs/core"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babylonjs_core__WEBPACK_IMPORTED_MODULE_3__); + + + + +function registerAttributeLessInstancingTests( +describe, +it, +enabled) +{ + describe("NativeEngine attribute-less instancing", function () { + (enabled ? it : it.skip)("draws every instance without an instance buffer", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {var engine, scene, camera, material, target, _i, _arr, unIndexed, mesh, _loop, _i2, _arr2;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context2) {while (1) switch (_context2.prev = _context2.next) {case 0: + this.timeout(15000); + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context2.prev = 1; + + scene.clearColor = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Color4(0, 0, 0, 1); + camera = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.FreeCamera("camera", new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Vector3(0, 0, -5), scene); + camera.setTarget(_babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Vector3.Zero()); + material = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.ShaderMaterial("instances", scene, { + vertexSource: "\n precision highp float;\n attribute vec3 position;\n varying vec3 instanceColor;\n void main() {\n float offset = (float(gl_InstanceID) - 1.0) * 0.6;\n gl_Position = vec4(position.x * 0.2 + offset, position.y * 0.2, 0.5, 1.0);\n instanceColor = vec3(float(gl_InstanceID == 0), float(gl_InstanceID == 1), float(gl_InstanceID == 2));\n }", + + + + + + + + + fragmentSource: "\n precision highp float;\n varying vec3 instanceColor;\n void main() { gl_FragColor = vec4(instanceColor, 1.0); }" + + + + }, { attributes: ["position"], uniforms: [] }); + material.backFaceCulling = false; + target = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.RenderTargetTexture("instances", 64, scene, false);_i = 0, _arr = + [false, true];case 2:if (!(_i < _arr.length)) {_context2.next = 7;break;}unIndexed = _arr[_i]; + mesh = _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.MeshBuilder.CreatePlane("plane", { size: 2 }, scene); + if (unIndexed) { + mesh.convertToUnIndexedMesh(); + } + mesh.material = material; + target.renderList = [mesh];_loop = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _loop() {var count, readinessDeadline, pixels, expected, centers, actual;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: + count = _arr2[_i2]; + mesh.forcedInstanceCount = count;_context.next = 1;return ( + material.forceCompilationAsync(mesh, { useInstances: count > 0 }));case 1:_context.next = 2;return ( + scene.whenReadyAsync());case 2: + readinessDeadline = Date.now() + 10000;case 3:if ( + target.isReadyForRendering()) {_context.next = 6;break;}if (!( + Date.now() >= readinessDeadline)) {_context.next = 4;break;}throw ( + new Error("Render target did not become ready: unIndexed=".concat(unIndexed, ", count=").concat(count)));case 4:_context.next = 5;return ( + + new Promise(function (resolve) {return setTimeout(resolve, 10);}));case 5:_context.next = 3;break;case 6: + + target.render();_context.next = 7;return ( + target.readPixels());case 7:pixels = _context.sent;if ( + pixels instanceof Uint8Array) {_context.next = 8;break;}throw ( + new Error("Expected RGBA8 instance rendering readback"));case 8: + + expected = [ + [255, 0, 0, 255], + count === 3 ? [0, 255, 0, 255] : [0, 0, 0, 255], + count === 3 ? [0, 0, 255, 255] : [0, 0, 0, 255]]; + + centers = [13, 32, 51]; + actual = centers.map(function (center) { + var offset = (32 * 64 + center) * 4; + return Array.from(pixels.subarray(offset, offset + 4)); + }); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(actual, "unIndexed=".concat(unIndexed, ", count=").concat(count)).to.deep.equal(expected);case 9:case "end":return _context.stop();}}, _loop);});_i2 = 0, _arr2 = [0, 1, 3];case 3:if (!(_i2 < _arr2.length)) {_context2.next = 5;break;}return _context2.delegateYield(_loop(), "t0", 4);case 4:_i2++;_context2.next = 3;break;case 5: + + mesh.dispose();case 6:_i++;_context2.next = 2;break;case 7:_context2.prev = 7; + + + scene.dispose(); + engine.dispose();return _context2.finish(7);case 8:case "end":return _context2.stop();}}, _callee, this, [[1,, 7, 8]]);})) + + ); + }); +} + +/***/ }, + +/***/ "./src/tests.nativeEngine.canvasImage.ts" +/*!***********************************************!*\ + !*** ./src/tests.nativeEngine.canvasImage.ts ***! + \***********************************************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ registerCanvasImageTests: () => (/* binding */ registerCanvasImageTests) +/* harmony export */ }); +/* harmony import */ var _babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/asyncToGenerator */ "../../node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js"); +/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @babel/runtime/regenerator */ "../../node_modules/@babel/runtime/regenerator/index.js"); +/* harmony import */ var _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var chai__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! chai */ "../../node_modules/chai/index.js"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @babylonjs/core */ "@babylonjs/core"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_babylonjs_core__WEBPACK_IMPORTED_MODULE_3__); + + + + + + +var opaqueOrangePng = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAFUlEQVR4nGP838DwnwEJMCFziBMAAKTRAobDsKmdAAAAAElFTkSuQmCC"; + +function fillImageData(context, width, height, pixel) {var dx = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;var dy = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 0; + var imageData = context.getImageData(0, 0, width, height); + for (var y = 0; y < height; ++y) { + for (var x = 0; x < width; ++x) { + imageData.data.set(pixel(x, y), (y * width + x) * 4); + } + } + context.putImageData(imageData, dx, dy); +}function + +readTexture(_x) {return _readTexture.apply(this, arguments);}function _readTexture() {_readTexture = (0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee5(texture) {var pixels, _texture$getSize, width, height, stride, topDown, y;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context5) {while (1) switch (_context5.prev = _context5.next) {case 0: + texture.update(false);_context5.next = 1;return ( + texture.readPixels());case 1:pixels = _context5.sent;if ( + pixels instanceof Uint8Array) {_context5.next = 2;break;}throw ( + new Error("Expected RGBA8 GPU readback for the canvas texture"));case 2: + + // Native readback uses WebGL's bottom-up row order; assertions use Canvas coordinates. + _texture$getSize = texture.getSize(), width = _texture$getSize.width, height = _texture$getSize.height; + stride = width * 4; + topDown = new Uint8Array(pixels.length); + for (y = 0; y < height; ++y) { + topDown.set(pixels.subarray(y * stride, (y + 1) * stride), (height - 1 - y) * stride); + }return _context5.abrupt("return", + topDown);case 3:case "end":return _context5.stop();}}, _callee5);}));return _readTexture.apply(this, arguments);} + + +function registerCanvasImageTests( +describe, +it, +skipCanvasGpuTests) +{ + describe("NativeEngine Canvas pixel-backed images", function () { + this.timeout(10000); + var test = skipCanvasGpuTests ? it.skip : it; + + test("keeps putImageData pixels alive through the GPU flush", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {var engine, scene, texture, pixels, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context.prev = 1; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.DynamicTexture( + "putImageData lifetime", + { width: 10, height: 8 }, + scene, + false, + _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + fillImageData(texture.getContext(), 4, 3, function () {return [0, 255, 255, 255];}, 3, 2);_context.next = 2;return ( + + readTexture(texture));case 2:pixels = _context.sent; + pixel = function pixel(x, y) {return Array.from(pixels.subarray((y * 10 + x) * 4, (y * 10 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 3), "inside putImageData rectangle").to.deep.equal([0, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(2, 3), "left of putImageData rectangle").to.deep.equal([0, 0, 0, 0]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 5), "below putImageData rectangle").to.deep.equal([0, 0, 0, 0]);case 3:_context.prev = 3; + + scene.dispose(); + engine.dispose();return _context.finish(3);case 4:case "end":return _context.stop();}}, _callee, null, [[1,, 3, 4]]);})) + + ); + + test("draws an offscreen canvas at a nonzero destination", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee2() {var engine, scene, source, texture, _pixels, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context2) {while (1) switch (_context2.prev = _context2.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context2.prev = 1; + + source = engine.createCanvas(4, 4); + fillImageData(source.getContext("2d"), 4, 4, function () {return [255, 0, 255, 255];}); + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.DynamicTexture( + "offscreen canvas copy", + { width: 12, height: 10 }, + scene, + false, + _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(source, 3, 2);_context2.next = 2;return ( + + readTexture(texture));case 2:_pixels = _context2.sent; + pixel = function pixel(x, y) {return Array.from(_pixels.subarray((y * 12 + x) * 4, (y * 12 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 3), "inside copied canvas").to.deep.equal([255, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(2, 3), "left of copied canvas").to.deep.equal([0, 0, 0, 0]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 6), "below copied canvas").to.deep.equal([0, 0, 0, 0]);case 3:_context2.prev = 3; + + scene.dispose(); + engine.dispose();return _context2.finish(3);case 4:case "end":return _context2.stop();}}, _callee2, null, [[1,, 3, 4]]);})) + + ); + + test("draws an ImageBitmap source through the GPU path", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee3() {var engine, scene, bitmap, texture, _pixels2, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context3) {while (1) switch (_context3.prev = _context3.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context3.prev = 1;_context3.next = 2;return ( + + engine._createImageBitmapFromSource(opaqueOrangePng));case 2:bitmap = _context3.sent; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.width).to.equal(4); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.height).to.equal(4); + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.DynamicTexture( + "ImageBitmap copy", + { width: 10, height: 8 }, + scene, + false, + _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(bitmap, 3, 2);_context3.next = 3;return ( + + readTexture(texture));case 3:_pixels2 = _context3.sent; + pixel = function pixel(x, y) {return Array.from(_pixels2.subarray((y * 10 + x) * 4, (y * 10 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 3), "inside copied ImageBitmap").to.deep.equal([255, 128, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(2, 3), "left of copied ImageBitmap").to.deep.equal([0, 0, 0, 0]);case 4:_context3.prev = 4; + + scene.dispose(); + engine.dispose();return _context3.finish(4);case 5:case "end":return _context3.stop();}}, _callee3, null, [[1,, 4, 5]]);})) + + ); + + test("crops and scales a nonzero offscreen-canvas source rectangle", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee4() {var engine, scene, source, texture, _pixels3, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context4) {while (1) switch (_context4.prev = _context4.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context4.prev = 1; + + source = engine.createCanvas(18, 8); + fillImageData(source.getContext("2d"), 18, 8, function (x) {return ( + x < 6 ? [255, 0, 0, 255] : + x < 12 ? [0, 255, 0, 255] : + [0, 0, 255, 255]);} + ); + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.DynamicTexture( + "offscreen canvas crop", + { width: 14, height: 12 }, + scene, + false, + _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Constants.TEXTURE_NEAREST_SAMPLINGMODE + ); + texture.getContext().drawImage(source, 7, 2, 4, 4, 3, 2, 8, 8);_context4.next = 2;return ( + + readTexture(texture));case 2:_pixels3 = _context4.sent; + pixel = function pixel(x, y) {return Array.from(_pixels3.subarray((y * 14 + x) * 4, (y * 14 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(4, 5), "left interior of scaled crop").to.deep.equal([0, 255, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(9, 5), "right interior of scaled crop").to.deep.equal([0, 255, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(pixel(2, 5), "outside destination rectangle").to.deep.equal([0, 0, 0, 0]);case 3:_context4.prev = 3; + + scene.dispose(); + engine.dispose();return _context4.finish(3);case 4:case "end":return _context4.stop();}}, _callee4, null, [[1,, 3, 4]]);})) + + ); + }); +} + +/***/ }, + /***/ "./src/tests.nativeEngine.png.ts" /*!***************************************!*\ !*** ./src/tests.nativeEngine.png.ts ***! @@ -24,33 +296,73 @@ function _createForOfIteratorHelper(r, e) {var t = "undefined" != typeof Symbol var fixtures = [ { - name: "16-bit RGBA", width: 4, tolerance: 1, + name: "16-bit RGBA", + tolerance: 1, png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAYAAACprNOOAAAAI0lEQVR4nGOo/38v9Ene//8MDP//NzBAeAwMIB4D4/9/90IBNYURsvcM43UAAAAASUVORK5CYII=", pixels: [128, 222, 227, 255, 0, 255, 128, 128, 222, 227, 0, 0, 255, 0, 255, 222] }, { - name: "16-bit RGB", width: 4, tolerance: 1, + name: "16-bit RGB", + tolerance: 1, png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAIAAAAmzkTZAAAAG0lEQVR4nGOo/38v9EkeA8P//w0MMBYD4/9/AKWbDQOAUd17AAAAAElFTkSuQmCC", pixels: [128, 222, 227, 255, 0, 255, 128, 255, 222, 227, 0, 255, 255, 0, 255, 255] }, { - name: "16-bit grayscale", width: 4, tolerance: 1, + name: "16-bit grayscale", + tolerance: 1, png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAAAAACMx4xSAAAAEUlEQVR4nGOo/38v9Ene//8AGa4GAvbhooAAAAAASUVORK5CYII=", pixels: [128, 128, 128, 255, 222, 222, 222, 255, 227, 227, 227, 255, 255, 255, 255, 255] }, { - name: "16-bit grayscale with alpha", width: 4, tolerance: 1, + name: "16-bit grayscale with alpha", + tolerance: 1, png: "iVBORw0KGgoAAAANSUhEUgAAAAQAAAABEAQAAAADpRsFAAAAF0lEQVR4nGOo/////73Q+v9P8hgYQCwAYP8KsdIXi8oAAAAASUVORK5CYII=", pixels: [128, 128, 128, 255, 222, 222, 222, 128, 227, 227, 227, 0, 255, 255, 255, 222] }, { - name: "8-bit opaque grayscale", width: 3, + name: "8-bit RGB color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAIAAACUgoPjAAAABnRSTlMAAAAAAABupgeRAAAAD0lEQVR42mNgAAFGLhE5AAByAD7AYQaiAAAAAElFTkSuQmCC", + width: 3, + pixels: [0, 0, 0, 0, 0, 0, 1, 255, 10, 20, 30, 255] +}, +{ + name: "8-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAAAnRSTlMAf7YpoZUAAAAMSURBVHjaY2Co/w8AAgEBf4sbZGEAAAAASUVORK5CYII=", + width: 3, + pixels: [0, 0, 0, 255, 127, 127, 127, 0, 255, 255, 255, 255] +}, +{ + name: "2-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAUAAAACAgAAAAD/sVEgAAAAAnRSTlMAApidrBQAAAAOSURBVHjaY5B2YJjcAAADMwFvhMae/wAAAABJRU5ErkJggg==", + width: 5, + height: 2, + pixels: [ + 0, 0, 0, 255, 85, 85, 85, 255, 170, 170, 170, 0, 255, 255, 255, 255, 85, 85, 85, 255, + 170, 170, 170, 0, 85, 85, 85, 255, 0, 0, 0, 255, 255, 255, 255, 255, 170, 170, 170, 0] + +}, +{ + name: "16-bit grayscale color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABEAAAAABuG5crAAAAAnRSTlMSNC/TSV4AAAAPSURBVHjaYxAyETJdfRYABIECBqQr4XkAAAAASUVORK5CYII=", + width: 3, + pixels: [18, 18, 18, 0, 18, 18, 18, 255, 171, 171, 171, 255] +}, +{ + name: "16-bit RGB color key", + png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABEAIAAADEEl+gAAAABnRSTlMSNFZ4mryJ5E7mAAAAHElEQVR42mMQMgmrmLVHyDSsctbe1WdVMv7dAQBEhQi2aXh0TAAAAABJRU5ErkJggg==", + width: 3, + pixels: [18, 86, 154, 0, 18, 86, 154, 255, 171, 36, 254, 255] +}, +{ + name: "8-bit opaque grayscale", png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAADElEQVR42mNgqP8PAAIBAX+LG2RhAAAAAElFTkSuQmCC", + width: 3, pixels: [0, 0, 0, 255, 127, 127, 127, 255, 255, 255, 255, 255] }, { - name: "8-bit grayscale with alpha", width: 3, + name: "8-bit grayscale with alpha", png: "iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAQAAACx6dw/AAAAD0lEQVR4nGNgYKh3+P8fAAXAAr4pW6ZDAAAAAElFTkSuQmCC", + width: 3, pixels: [0, 0, 0, 0, 127, 127, 127, 64, 255, 255, 255, 255] }]; @@ -73,9 +385,11 @@ enabled) describe("NativeEngine PNG decoding", function () { this.timeout(10000); var test = enabled ? it : it.skip;var _iterator = _createForOfIteratorHelper( - fixtures),_step;try {var _loop = function _loop() {var fixture = _step.value;var _loop2 = function _loop2() + fixtures),_step;try {var _loop = function _loop() {var _fixture$width, _fixture$height;var fixture = _step.value; + var width = (_fixture$width = fixture.width) !== null && _fixture$width !== void 0 ? _fixture$width : 4; + var height = (_fixture$height = fixture.height) !== null && _fixture$height !== void 0 ? _fixture$height : 1;var _loop2 = function _loop2() {var generateMips = _arr[_i]; - test("uploads ".concat(fixture.name, " PNG as RGBA8 (mips ").concat(generateMips, ")"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {var engine, scene, texture, pixels, mipWidth, mip;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: + test("uploads ".concat(fixture.name, " PNG as browser RGBA8 (mips ").concat(generateMips, ")"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee() {var engine, scene, texture, pixels, mipWidth, mipHeight, mip;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine(); scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.Scene(engine);_context.prev = 1;_context.next = 2;return ( @@ -91,13 +405,15 @@ enabled) new Error("Expected unsigned-byte PNG texture readback"));case 4: expectPixels(pixels, fixture); - (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(texture.getSize().width).to.equal(fixture.width); - (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(texture.getSize().height).to.equal(1);if (! + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(texture.getSize().width).to.equal(width); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(texture.getSize().height).to.equal(height);if (! generateMips) {_context.next = 6;break;} - mipWidth = Math.max(1, fixture.width >> 1);_context.next = 5;return ( - texture.readPixels(0, 1, null, true, false, 0, 0, mipWidth, 1));case 5:mip = _context.sent; + mipWidth = Math.max(1, width >> 1); + mipHeight = Math.max(1, height >> 1); + // Read only the valid mip extent for odd-sized PNG fixtures. + _context.next = 5;return texture.readPixels(0, 1, null, true, false, 0, 0, mipWidth, mipHeight);case 5:mip = _context.sent; (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(mip instanceof Uint8Array).to.equal(true); - (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(mip === null || mip === void 0 ? void 0 : mip.byteLength).to.equal(mipWidth * 4);case 6:_context.prev = 6; + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(mip === null || mip === void 0 ? void 0 : mip.byteLength).to.equal(mipWidth * mipHeight * 4);case 6:_context.prev = 6; scene.dispose(); @@ -105,14 +421,13 @@ enabled) ); };for (var _i = 0, _arr = [false, true]; _i < _arr.length; _i++) {_loop2();} - test("decodes ".concat(fixture.name, " PNG through Canvas images"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_0__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().mark(function _callee2() {var engine, bitmap, pixels;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_1___default().wrap(function (_context2) {while (1) switch (_context2.prev = _context2.next) {case 0: engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_3__.NativeEngine();_context2.prev = 1;_context2.next = 2;return ( engine._createImageBitmapFromSource("data:image/png;base64," + fixture.png));case 2:bitmap = _context2.sent; - (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.width).to.equal(fixture.width); - (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.height).to.equal(1); - pixels = engine.resizeImageBitmap(bitmap, fixture.width, 1); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.width).to.equal(width); + (0,chai__WEBPACK_IMPORTED_MODULE_2__.expect)(bitmap.height).to.equal(height); + pixels = engine.resizeImageBitmap(bitmap, width, height); expectPixels(pixels, fixture);case 3:_context2.prev = 3; engine.dispose();return _context2.finish(3);case 4:case "end":return _context2.stop();}}, _callee2, null, [[1,, 3, 4]]);})) @@ -28517,10 +28832,12 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var mocha__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! mocha */ "../../node_modules/mocha/browser-entry.js"); /* harmony import */ var mocha__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(mocha__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var chai__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! chai */ "../../node_modules/chai/index.js"); -/* harmony import */ var buffer__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! buffer */ "../../node_modules/buffer/index.js"); -/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @babylonjs/materials */ "@babylonjs/core"); -/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(_babylonjs_core__WEBPACK_IMPORTED_MODULE_6__); -/* harmony import */ var _tests_nativeEngine_png__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./tests.nativeEngine.png */ "./src/tests.nativeEngine.png.ts"); +/* harmony import */ var _tests_nativeEngine_png__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./tests.nativeEngine.png */ "./src/tests.nativeEngine.png.ts"); +/* harmony import */ var _tests_nativeEngine_canvasImage__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./tests.nativeEngine.canvasImage */ "./src/tests.nativeEngine.canvasImage.ts"); +/* harmony import */ var buffer__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! buffer */ "../../node_modules/buffer/index.js"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @babylonjs/materials */ "@babylonjs/core"); +/* harmony import */ var _babylonjs_core__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(_babylonjs_core__WEBPACK_IMPORTED_MODULE_8__); +/* harmony import */ var _tests_nativeEngine_attributeLessInstancing__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./tests.nativeEngine.attributeLessInstancing */ "./src/tests.nativeEngine.attributeLessInstancing.ts"); function _createForOfIteratorHelper(r, e) {var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];if (!t) {if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {t && (r = t);var _n = 0,F = function F() {};return { s: F, n: function n() {return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] };}, e: function e(r) {throw r;}, f: F };}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");}var o,a = !0,u = !1;return { s: function s() {t = t.call(r);}, n: function n() {var r = t.next();return a = r.done, r;}, e: function e(r) {u = !0, o = r;}, f: function f() {try {a || null == t.return || t.return();} finally {if (u) throw o;}} };}function _unsupportedIterableToArray(r, a) {if (r) {if ("string" == typeof r) return _arrayLikeToArray(r, a);var t = {}.toString.call(r).slice(8, -1);return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;}}function _arrayLikeToArray(r, a) {(null == a || a > r.length) && (a = r.length);for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];return n;} @@ -28531,6 +28848,8 @@ function _createForOfIteratorHelper(r, e) {var t = "undefined" != typeof Symbol + + mocha__WEBPACK_IMPORTED_MODULE_3__.setup("bdd"); // @ts-ignore mocha__WEBPACK_IMPORTED_MODULE_3__.reporter("spec"); @@ -28542,18 +28861,49 @@ mocha__WEBPACK_IMPORTED_MODULE_3__.reporter("spec"); -(0,_tests_nativeEngine_png__WEBPACK_IMPORTED_MODULE_7__.registerPngTests)(describe, it, hasGpuRendering && hasNativeImageLoading); + +(0,_tests_nativeEngine_png__WEBPACK_IMPORTED_MODULE_5__.registerPngTests)(describe, it, hasGpuRendering && hasNativeImageLoading); +(0,_tests_nativeEngine_attributeLessInstancing__WEBPACK_IMPORTED_MODULE_9__.registerAttributeLessInstancingTests)(describe, it, hasAttributeLessInstancing); describe("Native texture readback", function () { this.timeout(10000); // Native LoadRawTexture is also disabled when native image loading is off. var itWithRawTexture = hasGpuRendering && hasNativeImageLoading ? it : it.skip; - itWithRawTexture("returns bottom-origin RGBA8 crops and preserves destination offsets", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee() {var engine, scene, _i, _arr, invertY, width, height, data, y, x, texture, _i2, _arr2, _arr2$_i, _x, _y, readWidth, readHeight, storage, destination, result, row, sourceY, column, offset;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context.prev = 1;_i = 0, _arr = + itWithRawTexture("rejects invalid native buffer and face numbers without modifying the destination", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee() {var engine, texture, _i, _arr, component, _i2, _arr2, invalid, destination, request, error, _t;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context) {while (1) switch (_context.prev = _context.next) {case 0: + engine = new _native.Engine(); + texture = engine.createTexture();_context.prev = 1; + + engine.initializeTexture(texture, 4, 4, false, _native.Engine.TEXTURE_FORMAT_RGBA8, false, false, 1);_i = 0, _arr = + [7, 8, 9];case 2:if (!(_i < _arr.length)) {_context.next = 11;break;}component = _arr[_i];_i2 = 0, _arr2 = + [-2, 0.5, NaN, Infinity, Math.pow(2, 32), Math.pow(2, 32) + 1];case 3:if (!(_i2 < _arr2.length)) {_context.next = 10;break;}invalid = _arr2[_i2]; + destination = new Uint8Array(4).fill(91); + request = [texture, 0, 0, 0, 1, 1, destination.buffer, 0, 4, -1]; + request[component] = invalid; + error = void 0;_context.prev = 4;_context.next = 5;return ( + + engine.readTexture.apply(engine, request));case 5:_context.next = 7;break;case 6:_context.prev = 6;_t = _context["catch"](4); + + error = _t;case 7:if ( + + error instanceof Error) {_context.next = 8;break;}throw ( + new Error("Expected native readback component ".concat(component, "=").concat(invalid, " to reject")));case 8: + + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(error.message).to.contain(component === 9 ? "face/layer index" : "buffer offset and length"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(destination)).to.deep.equal([91, 91, 91, 91]);case 9:_i2++;_context.next = 3;break;case 10:_i++;_context.next = 2;break;case 11:_context.prev = 11; - [false, true];case 2:if (!(_i < _arr.length)) {_context.next = 9;break;}invertY = _arr[_i]; + + + engine.deleteTexture(texture); + engine.dispose();return _context.finish(11);case 12:case "end":return _context.stop();}}, _callee, null, [[1,, 11, 12], [4, 6]]);})) + + ); + + itWithRawTexture("returns bottom-origin RGBA8 crops and preserves destination offsets", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee2() {var engine, scene, _i3, _arr3, invertY, width, height, data, y, x, texture, _i4, _arr4, _arr4$_i, _x, _y, readWidth, readHeight, storage, destination, result, row, sourceY, column, offset;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context2) {while (1) switch (_context2.prev = _context2.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context2.prev = 1;_i3 = 0, _arr3 = + + [false, true];case 2:if (!(_i3 < _arr3.length)) {_context2.next = 9;break;}invertY = _arr3[_i3]; width = 5; height = 7; data = new Uint8Array(width * height * 4); @@ -28562,15 +28912,15 @@ describe("Native texture readback", function () { data.set([x * 40, y * 30, 128, 255], (y * width + x) * 4); } } - texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.RawTexture.CreateRGBATexture(data, width, height, scene, false, invertY);_context.prev = 3;_i2 = 0, _arr2 = + texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RawTexture.CreateRGBATexture(data, width, height, scene, false, invertY);_context2.prev = 3;_i4 = 0, _arr4 = [ [0, 0, width, height], [0, 0, 1, 1], [4, 6, 1, 1], - [1, 0, 3, 2], [1, 2, 2, 3], [0, 5, 5, 2]];case 4:if (!(_i2 < _arr2.length)) {_context.next = 7;break;}_arr2$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr2[_i2], 4), _x = _arr2$_i[0], _y = _arr2$_i[1], readWidth = _arr2$_i[2], readHeight = _arr2$_i[3]; + [1, 0, 3, 2], [1, 2, 2, 3], [0, 5, 5, 2]];case 4:if (!(_i4 < _arr4.length)) {_context2.next = 7;break;}_arr4$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr4[_i4], 4), _x = _arr4$_i[0], _y = _arr4$_i[1], readWidth = _arr4$_i[2], readHeight = _arr4$_i[3]; storage = new Uint8Array(readWidth * readHeight * 4 + 12).fill(91); - destination = storage.subarray(4, storage.length - 8);_context.next = 5;return ( - texture.readPixels(0, 0, destination, true, false, _x, _y, readWidth, readHeight));case 5:result = _context.sent; + destination = storage.subarray(4, storage.length - 8);_context2.next = 5;return ( + texture.readPixels(0, 0, destination, true, false, _x, _y, readWidth, readHeight));case 5:result = _context2.sent; (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result).to.equal(destination); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(storage.subarray(0, 4))).to.deep.equal([91, 91, 91, 91]); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(storage.subarray(storage.length - 8))).to.deep.equal(Array(8).fill(91)); @@ -28582,21 +28932,21 @@ describe("Native texture readback", function () { invertY, ", crop=").concat(_x, ",").concat(_y, ",").concat(readWidth, ",").concat(readHeight, ", pixel=").concat(column, ",").concat(row)). to.deep.equal([(_x + column) * 40, sourceY * 30, 128, 255]); } - }case 6:_i2++;_context.next = 4;break;case 7:_context.prev = 7; + }case 6:_i4++;_context2.next = 4;break;case 7:_context2.prev = 7; - texture.dispose();return _context.finish(7);case 8:_i++;_context.next = 2;break;case 9:_context.prev = 9; + texture.dispose();return _context2.finish(7);case 8:_i3++;_context2.next = 2;break;case 9:_context2.prev = 9; scene.dispose(); - engine.dispose();return _context.finish(9);case 10:case "end":return _context.stop();}}, _callee, null, [[1,, 9, 10], [3,, 7, 8]]);})) + engine.dispose();return _context2.finish(9);case 10:case "end":return _context2.stop();}}, _callee2, null, [[1,, 9, 10], [3,, 7, 8]]);})) ); - itWithRawTexture("returns wide odd-height readbacks without changing the middle row", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee2() {var engine, scene, width, height, rowPitch, data, y, x, _i3, _arr3, invertY, texture, result, row, sourceY;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context2) {while (1) switch (_context2.prev = _context2.next) {case 0: - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context2.prev = 1; + itWithRawTexture("returns wide odd-height readbacks without changing the middle row", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee3() {var engine, scene, width, height, rowPitch, data, y, x, _i5, _arr5, invertY, texture, result, row, sourceY;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context3) {while (1) switch (_context3.prev = _context3.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context3.prev = 1; width = 2049; height = 3; @@ -28606,12 +28956,12 @@ describe("Native texture readback", function () { for (x = 0; x < width; ++x) { data.set([x % 251, y * 70, Math.floor(x / 256) * 23, 255], y * rowPitch + x * 4); } - }_i3 = 0, _arr3 = - [false, true];case 2:if (!(_i3 < _arr3.length)) {_context2.next = 8;break;}invertY = _arr3[_i3]; - texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.RawTexture.CreateRGBATexture(data, width, height, scene, false, invertY);_context2.prev = 3;_context2.next = 4;return ( + }_i5 = 0, _arr5 = + [false, true];case 2:if (!(_i5 < _arr5.length)) {_context3.next = 8;break;}invertY = _arr5[_i5]; + texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RawTexture.CreateRGBATexture(data, width, height, scene, false, invertY);_context3.prev = 3;_context3.next = 4;return ( - texture.readPixels());case 4:result = _context2.sent;if ( - result instanceof Uint8Array) {_context2.next = 5;break;}throw ( + texture.readPixels());case 4:result = _context3.sent;if ( + result instanceof Uint8Array) {_context3.next = 5;break;}throw ( new Error("Expected RGBA8 wide readback"));case 5: (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result.length).to.equal(data.length); @@ -28619,20 +28969,20 @@ describe("Native texture readback", function () { sourceY = invertY ? height - 1 - row : row; (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(result.subarray(row * rowPitch, (row + 1) * rowPitch))). to.deep.equal(Array.from(data.subarray(sourceY * rowPitch, (sourceY + 1) * rowPitch))); - }case 6:_context2.prev = 6; + }case 6:_context3.prev = 6; - texture.dispose();return _context2.finish(6);case 7:_i3++;_context2.next = 2;break;case 8:_context2.prev = 8; + texture.dispose();return _context3.finish(6);case 7:_i5++;_context3.next = 2;break;case 8:_context3.prev = 8; scene.dispose(); - engine.dispose();return _context2.finish(8);case 9:case "end":return _context2.stop();}}, _callee2, null, [[1,, 8, 9], [3,, 6, 7]]);})) + engine.dispose();return _context3.finish(8);case 9:case "end":return _context3.stop();}}, _callee3, null, [[1,, 8, 9], [3,, 6, 7]]);})) ); - itWithRawTexture("uses mip extents for cropped readback", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee3() {var engine, scene, data, y, x, texture, _i4, _arr4, _arr4$_i, _y2, green, result, internalTexture, _i5, _arr5, _arr5$_i, _x2, _y3, width, height, error, _i6, _arr6, invalid, _i7, _arr7, component, request, mip, _x3, _y4, _width, _height, destination, _error, maximum, parameter, _t, _t2;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context3) {while (1) switch (_context3.prev = _context3.next) {case 0: - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context3.prev = 1; + itWithRawTexture("uses mip extents for cropped readback", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee4() {var engine, scene, data, y, x, texture, _i6, _arr6, _arr6$_i, _y2, green, result, internalTexture, _i7, _arr7, _arr7$_i, _x2, _y3, width, height, error, _i8, _arr8, invalid, _i9, _arr9, component, request, mip, _x3, _y4, _width, _height, destination, _error, maximum, parameter, _t2, _t3;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context4) {while (1) switch (_context4.prev = _context4.next) {case 0: + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context4.prev = 1; data = new Uint8Array(4 * 8 * 4); for (y = 0; y < 8; ++y) { @@ -28640,10 +28990,10 @@ describe("Native texture readback", function () { data.set([x < 2 ? 255 : 0, y < 4 ? 255 : 0, 0, 255], (y * 4 + x) * 4); } } - texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.RawTexture.CreateRGBATexture(data, 4, 8, scene, true, false);_i4 = 0, _arr4 = - [[0, 255], [3, 0]];case 2:if (!(_i4 < _arr4.length)) {_context3.next = 6;break;}_arr4$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr4[_i4], 2), _y2 = _arr4$_i[0], green = _arr4$_i[1];_context3.next = 3;return ( - texture.readPixels(0, 1, null, true, false, 1, _y2, 1, 1));case 3:result = _context3.sent;if ( - result instanceof Uint8Array) {_context3.next = 4;break;}throw ( + texture = _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RawTexture.CreateRGBATexture(data, 4, 8, scene, true, false);_i6 = 0, _arr6 = + [[0, 255], [3, 0]];case 2:if (!(_i6 < _arr6.length)) {_context4.next = 6;break;}_arr6$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr6[_i6], 2), _y2 = _arr6$_i[0], green = _arr6$_i[1];_context4.next = 3;return ( + texture.readPixels(0, 1, null, true, false, 1, _y2, 1, 1));case 3:result = _context4.sent;if ( + result instanceof Uint8Array) {_context4.next = 4;break;}throw ( new Error("Expected RGBA8 mip readback"));case 4: (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result.length).to.equal(4); @@ -28651,40 +29001,40 @@ describe("Native texture readback", function () { // Mip generation can round a solid 255 channel down by one. (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result[1]).to.be.closeTo(green, 1); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result[2]).to.equal(0); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result[3]).to.equal(255);case 5:_i4++;_context3.next = 2;break;case 6: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(result[3]).to.equal(255);case 5:_i6++;_context4.next = 2;break;case 6: internalTexture = texture.getInternalTexture();if ( - internalTexture) {_context3.next = 7;break;}throw ( - new Error("Expected an initialized raw texture"));case 7:_i5 = 0, _arr5 = + internalTexture) {_context4.next = 7;break;}throw ( + new Error("Expected an initialized raw texture"));case 7:_i7 = 0, _arr7 = [ [2, 0, 1, 1], [0, 4, 1, 1], [0, 3, 1, 2], [0, 0, 0, 1], - [0, 0, 65535, 65535]];case 8:if (!(_i5 < _arr5.length)) {_context3.next = 15;break;}_arr5$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr5[_i5], 4), _x2 = _arr5$_i[0], _y3 = _arr5$_i[1], width = _arr5$_i[2], height = _arr5$_i[3]; + [0, 0, 65535, 65535]];case 8:if (!(_i7 < _arr7.length)) {_context4.next = 15;break;}_arr7$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr7[_i7], 4), _x2 = _arr7$_i[0], _y3 = _arr7$_i[1], width = _arr7$_i[2], height = _arr7$_i[3]; - error = void 0;_context3.prev = 9;_context3.next = 10;return ( + error = void 0;_context4.prev = 9;_context4.next = 10;return ( - engine._readTexturePixels(internalTexture, width, height, -1, 1, null, true, false, _x2, _y3));case 10:_context3.next = 12;break;case 11:_context3.prev = 11;_t = _context3["catch"](9); + engine._readTexturePixels(internalTexture, width, height, -1, 1, null, true, false, _x2, _y3));case 10:_context4.next = 12;break;case 11:_context4.prev = 11;_t2 = _context4["catch"](9); - error = _t;case 12:if ( + error = _t2;case 12:if ( - error instanceof Error) {_context3.next = 13;break;}throw ( + error instanceof Error) {_context4.next = 13;break;}throw ( new Error("Expected out-of-range mip readback to reject"));case 13: - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(error.message).to.contain("rectangle is out of range");case 14:_i5++;_context3.next = 8;break;case 15:_i6 = 0, _arr6 = + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(error.message).to.contain("rectangle is out of range");case 14:_i7++;_context4.next = 8;break;case 15:_i8 = 0, _arr8 = - [65536, 65537, 256, Math.pow(2, 32), Math.pow(2, 32) + 1, -1, 0.5, NaN, Infinity];case 16:if (!(_i6 < _arr6.length)) {_context3.next = 25;break;}invalid = _arr6[_i6];_i7 = 0, _arr7 = - [1, 2, 3, 4, 0];case 17:if (!(_i7 < _arr7.length)) {_context3.next = 24;break;}component = _arr7[_i7]; + [65536, 65537, 256, Math.pow(2, 32), Math.pow(2, 32) + 1, -1, 0.5, NaN, Infinity];case 16:if (!(_i8 < _arr8.length)) {_context4.next = 25;break;}invalid = _arr8[_i8];_i9 = 0, _arr9 = + [1, 2, 3, 4, 0];case 17:if (!(_i9 < _arr9.length)) {_context4.next = 24;break;}component = _arr9[_i9]; request = [0, 0, 0, 1, 1]; request[component] = invalid; mip = request[0], _x3 = request[1], _y4 = request[2], _width = request[3], _height = request[4]; destination = new Uint8Array(4).fill(91); - _error = void 0;_context3.prev = 18;_context3.next = 19;return ( + _error = void 0;_context4.prev = 18;_context4.next = 19;return ( - engine._readTexturePixels(internalTexture, _width, _height, -1, mip, destination, true, false, _x3, _y4));case 19:_context3.next = 21;break;case 20:_context3.prev = 20;_t2 = _context3["catch"](18); + engine._readTexturePixels(internalTexture, _width, _height, -1, mip, destination, true, false, _x3, _y4));case 19:_context4.next = 21;break;case 20:_context4.prev = 20;_t3 = _context4["catch"](18); - _error = _t2;case 21:if ( + _error = _t3;case 21:if ( - _error instanceof Error) {_context3.next = 22;break;}throw ( + _error instanceof Error) {_context4.next = 22;break;}throw ( new Error("Expected invalid readback component ".concat(component, "=").concat(invalid, " to reject")));case 22: maximum = component === 0 ? 255 : 65535; @@ -28694,21 +29044,22 @@ describe("Native texture readback", function () { } else { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(_error.message).to.contain("rectangle is out of range"); } - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(destination)).to.deep.equal([91, 91, 91, 91]);case 23:_i7++;_context3.next = 17;break;case 24:_i6++;_context3.next = 16;break;case 25:_context3.prev = 25; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(destination)).to.deep.equal([91, 91, 91, 91]);case 23:_i9++;_context4.next = 17;break;case 24:_i8++;_context4.next = 16;break;case 25:_context4.prev = 25; scene.dispose(); - engine.dispose();return _context3.finish(25);case 26:case "end":return _context3.stop();}}, _callee3, null, [[1,, 25, 26], [9, 11], [18, 20]]);})) + engine.dispose();return _context4.finish(25);case 26:case "end":return _context4.stop();}}, _callee4, null, [[1,, 25, 26], [9, 11], [18, 20]]);})) ); }); +(0,_tests_nativeEngine_canvasImage__WEBPACK_IMPORTED_MODULE_6__.registerCanvasImageTests)(describe, it, skipCanvasGpuTests); describe("RequestFile", function () { this.timeout(0); it("should throw when requesting a URL with no protocol", function () { function requestFile() { - (0,_babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.RequestFile)("noprotocol.gltf", function () {}); + (0,_babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RequestFile)("noprotocol.gltf", function () {}); } (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(requestFile).to.throw(); }); @@ -28817,6 +29168,22 @@ describe("ColorParsing", function () { }); }); +describe("Native splat matrix storage", function () { + var test = typeof _native.sortSplats === "function" ? it : it.skip;var _loop = function _loop() + {var numberArray = _arr0[_i0]; + test("sorts with ".concat(numberArray ? "number-array" : "Float32Array", " matrices"), function () { + var values = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + var matrix = { _m: numberArray ? values : new Float32Array(values) }; + var positions = new Float32Array([0, 0, 1, 1, 0, 0, 4, 1, 0, 0, 2, 1]); + for (var _i1 = 0, _arr1 = [false, true]; _i1 < _arr1.length; _i1++) {var rightHanded = _arr1[_i1]; + var indices = new Float32Array(3); + _native.sortSplats(matrix, positions, indices, rightHanded); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(indices)).to.deep.equal(rightHanded ? [0, 2, 1] : [1, 2, 0]); + } + }); + };for (var _i0 = 0, _arr0 = [false, true]; _i0 < _arr0.length; _i0++) {_loop();} +}); + describe("Canvas2D", function () { // No-op renderers accept GPU commands but cannot produce pixels for readback. var itWithGpu = hasGpuRendering ? it : it.skip; @@ -28828,6 +29195,399 @@ describe("Canvas2D", function () { return canvas.getContext("2d"); } + // Font registrations are global; exercise the fallback before loading a face. + it("returns ascent and descent in no-font text metrics", function () { + var resource = createCanvas(8, 8); + try { + resource.context.font = "20px MissingFontForCanvasMetrics"; + var metrics = resource.context.measureText("test"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics).to.have.property("actualBoundingBoxAscent"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics).to.have.property("actualBoundingBoxDescent"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics.actualBoundingBoxAscent).to.equal(15); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics.actualBoundingBoxDescent).to.equal(5); + } finally { + disposeCanvas(resource); + } + }); + + (skipCanvasGpuTests ? it.skip : it)("intersects nested clips and restores parent clips on the GPU", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee5() {var _loop2, _i10, _arr10;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context6) {while (1) switch (_context6.prev = _context6.next) {case 0: + this.timeout(10000);_loop2 = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _loop2() {var translated, engine, scene, texture, ctx, pixels, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context5) {while (1) switch (_context5.prev = _context5.next) {case 0: + translated = _arr10[_i10]; + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context5.prev = 1; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("nested clips", 64, scene, false); + ctx = texture.getContext(); + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(8, 0, 24, 64); + ctx.clip(); + + ctx.save(); + if (translated) { + ctx.translate(16, 0); + } + ctx.beginPath(); + ctx.rect(0, 0, 64, 64); + ctx.clip(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + + ctx.fillStyle = "#00ff00"; + ctx.fillRect(24, 0, 16, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(40, 0, 16, 64); + ctx.clip(); + ctx.fillStyle = "magenta"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.restore(); + + ctx.fillStyle = "blue"; + ctx.fillRect(40, 0, 8, 64); + texture.update(false);_context5.next = 2;return ( + texture.readPixels());case 2:pixels = _context5.sent;if ( + pixels instanceof Uint8Array) {_context5.next = 3;break;}throw ( + new Error("Expected RGBA8 GPU readback for the canvas texture"));case 3: + + pixel = function pixel(x) {return Array.from(pixels.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(4), "outside parent").to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(12), "translated child boundary").to.deep.equal(translated ? [255, 255, 255, 255] : [255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(20), "inside intersection").to.deep.equal([255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(28), "restored parent").to.deep.equal([0, 255, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(36), "outside restored parent").to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(44), "restored unclipped state").to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(52), "disjoint clip").to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(60), "outside every fill").to.deep.equal([255, 255, 255, 255]);case 4:_context5.prev = 4; + + scene.dispose(); + engine.dispose();return _context5.finish(4);case 5:case "end":return _context5.stop();}}, _loop2, null, [[1,, 4, 5]]);});_i10 = 0, _arr10 = [false, true];case 1:if (!(_i10 < _arr10.length)) {_context6.next = 3;break;}return _context6.delegateYield(_loop2(), "t0", 2);case 2:_i10++;_context6.next = 1;break;case 3:case "end":return _context6.stop();}}, _callee5, this);})) + + + ); + + (skipCanvasGpuTests ? it.skip : it)("normalizes negative rectangle dimensions before intersecting GPU clips", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee6() {var engine, scene, texture, ctx, _i11, _arr11, rotated, expected, _i12, _arr12, _arr12$_i, flipX, flipY, _pixels, inside, changed, index;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context7) {while (1) switch (_context7.prev = _context7.next) {case 0: + this.timeout(10000); + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context7.prev = 1; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("signed clips", 64, scene, false); + ctx = texture.getContext();_i11 = 0, _arr11 = + [false, true];case 2:if (!(_i11 < _arr11.length)) {_context7.next = 8;break;}rotated = _arr11[_i11]; + expected = void 0;_i12 = 0, _arr12 = + [[false, false], [true, false], [false, true], [true, true]];case 3:if (!(_i12 < _arr12.length)) {_context7.next = 7;break;}_arr12$_i = (0,_babel_runtime_helpers_slicedToArray__WEBPACK_IMPORTED_MODULE_0__["default"])(_arr12[_i12], 2), flipX = _arr12$_i[0], flipY = _arr12$_i[1]; + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(18, 12, 36, 44); + ctx.clip(); + ctx.save(); + if (rotated) { + ctx.translate(32, 32); + ctx.rotate(0.3); + ctx.translate(-32, -32); + } + ctx.beginPath(); + ctx.rect(flipX ? 40 : 16, flipY ? 40 : 20, flipX ? -24 : 24, flipY ? -20 : 20); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.restore(); + texture.update(false);_context7.next = 4;return ( + + texture.readPixels());case 4:_pixels = _context7.sent;if ( + _pixels instanceof Uint8Array) {_context7.next = 5;break;}throw ( + new Error("Expected RGBA8 GPU readback for signed clips"));case 5: + + inside = (30 * 64 + 28) * 4; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(_pixels.subarray(inside, inside + 4)), "inside clip, rotated=".concat(rotated, ", flipX=").concat(flipX, ", flipY=").concat(flipY)).to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(_pixels.subarray(0, 4)), "outside parent clip").to.deep.equal([255, 255, 255, 255]); + if (expected) { + changed = 0; + for (index = 0; index < _pixels.length; ++index) { + if (_pixels[index] !== expected[index]) { + ++changed; + } + } + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(changed, "signed clip equivalence, rotated=".concat(rotated, ", flipX=").concat(flipX, ", flipY=").concat(flipY)).to.equal(0); + } else { + expected = _pixels.slice(); + }case 6:_i12++;_context7.next = 3;break;case 7:_i11++;_context7.next = 2;break;case 8:_context7.prev = 8; + + + + scene.dispose(); + engine.dispose();return _context7.finish(8);case 9:case "end":return _context7.stop();}}, _callee6, this, [[1,, 8, 9]]);})) + + ); + + (skipCanvasGpuTests ? it.skip : it)("clips text, fills, strokes and images to saved curved paths on the GPU", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee8() {var fontData, engine, scene, texture, source, ctx, read, _i13, _arr13, mode, _pixels2, changedInside, y, x, offset, changed, distance, restored, nested, pixel, _i14, _arr14, _mode, path, result, _center, filled, preserved, outside, center;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context9) {while (1) switch (_context9.prev = _context9.next) {case 0: + this.timeout(20000);_context9.next = 1;return ( + new Promise(function (resolve, reject) { + (0,_babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RequestFile)("app:///Assets/Arimo-Regular.ttf", function (data) { + if (typeof data === "string") { + reject(new Error("Expected binary clip-test font")); + } else { + resolve(data); + } + }, undefined, undefined, true, reject); + }));case 1:fontData = _context9.sent; + _native.Canvas.loadTTF("clip-test-font", fontData); + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context9.prev = 2; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("curved clips", 64, scene, false); + source = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("clip image", 64, scene, false); + source.getContext().fillStyle = "blue"; + source.getContext().fillRect(0, 0, 64, 64); + ctx = texture.getContext(); + read = /*#__PURE__*/function () {var _ref8 = (0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee7() {var pixels, topDown, y;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context8) {while (1) switch (_context8.prev = _context8.next) {case 0: + texture.update(false);_context8.next = 1;return ( + texture.readPixels());case 1:pixels = _context8.sent;if ( + pixels instanceof Uint8Array) {_context8.next = 2;break;}throw ( + new Error("Expected RGBA8 GPU readback for curved clips"));case 2: + + // Native readback is bottom-up; assertions below use Canvas coordinates. + topDown = new Uint8Array(pixels.length); + for (y = 0; y < 64; ++y) { + topDown.set(pixels.subarray(y * 64 * 4, (y + 1) * 64 * 4), (63 - y) * 64 * 4); + }return _context8.abrupt("return", + topDown);case 3:case "end":return _context8.stop();}}, _callee7);}));return function read() {return _ref8.apply(this, arguments);};}();_i13 = 0, _arr13 = + + ["text", "fill", "stroke", "image", "clear", "blur(1px)", "blur(3px)"];case 3:if (!(_i13 < _arr13.length)) {_context9.next = 8;break;}mode = _arr13[_i13]; + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.translate(32, 32); + ctx.rotate(0.2); + ctx.beginPath(); + ctx.arc(0, 0, 20, 0, Math.PI * 2); + ctx.clip(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + // A new path and a GPU flush must not erase the saved clipping region. + ctx.beginPath();_context9.next = 4;return ( + read());case 4: + ctx.fillStyle = "blue"; + ctx.strokeStyle = "blue"; + if (mode === "text") { + ctx.font = "24px clip-test-font"; + ctx.fillText("MMMM", 0, 24); + ctx.fillText("MMMM", 0, 48); + } else if (mode === "stroke") { + ctx.lineWidth = 12; + ctx.moveTo(0, 32); + ctx.lineTo(64, 32); + ctx.stroke(); + } else if (mode === "image") { + ctx.drawImage(source.getContext().canvas, 0, 0); + } else if (mode === "clear") { + ctx.clearRect(0, 0, 64, 64); + } else { + if (mode.startsWith("blur")) { + ctx.filter = mode; + } + ctx.fillRect(0, 0, 64, 64); + }_context9.next = 5;return ( + read());case 5:_pixels2 = _context9.sent; + changedInside = 0; + for (y = 0; y < 64; ++y) { + for (x = 0; x < 64; ++x) { + offset = (y * 64 + x) * 4; + changed = _pixels2.subarray(offset, offset + 4).some(function (value) {return value !== 255;}); + distance = Math.hypot(x + 0.5 - 32, y + 0.5 - 32); + if (distance > 22) { + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(changed, "".concat(mode, " outside curved clip at ").concat(x, ",").concat(y)).to.equal(false); + } else if (distance < 18 && changed) { + ++changedInside; + } + } + } + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(changedInside, "".concat(mode, " visible inside clip")).to.be.greaterThan(50); + ctx.restore(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 4, 4);_context9.next = 6;return ( + read());case 6:restored = _context9.sent; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(restored.subarray(0, 4)), "".concat(mode, " restore after multiple flushes")).to.deep.equal([255, 0, 0, 255]);case 7:_i13++;_context9.next = 3;break;case 8: + + + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.roundRect(8, 8, 48, 48, 16); + ctx.clip(); + ctx.save(); + ctx.beginPath(); + ctx.arc(24, 32, 14, 0, Math.PI * 2); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore(); + ctx.fillStyle = "#00ff00"; + ctx.fillRect(40, 8, 16, 48); + ctx.restore();_context9.next = 9;return ( + read());case 9:nested = _context9.sent; + pixel = function pixel(x, y) {return Array.from(nested.subarray((y * 64 + x) * 4, (y * 64 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(24, 32), "nested curved intersection").to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(48, 32), "restored rounded parent").to.deep.equal([0, 255, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(54, 10), "rounded corner stays clipped").to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(32, 12), "outside nested circle").to.deep.equal([255, 255, 255, 255]);_i14 = 0, _arr14 = + + ["nonzero", "evenodd", "opposite", "empty", "zero", "Path2D"];case 10:if (!(_i14 < _arr14.length)) {_context9.next = 13;break;}_mode = _arr14[_i14]; + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + if (_mode === "zero") { + ctx.rect(8, 8, 0, 48); + } else if (_mode !== "empty") { + ctx.rect(8, 8, 48, 48); + if (_mode === "opposite") { + ctx.rect(48, 16, -32, 32); + } else { + ctx.rect(16, 16, 32, 32); + } + } + if (_mode === "Path2D") { + path = new _native.Path2D(); + path.arc(32, 32, 20, 0, Math.PI * 2); + ctx.clip(path); + path.rect(0, 0, 64, 64); + } else { + ctx.clip(_mode === "evenodd" ? "evenodd" : "nonzero"); + } + // Immediate rectangles must not replace the path captured above. + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64);_context9.next = 11;return ( + read());case 11:result = _context9.sent; + _center = (32 * 64 + 32) * 4; + filled = _mode === "nonzero" || _mode === "Path2D"; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(result.subarray(_center, _center + 4)), "".concat(_mode, " clip center")).to.deep.equal(filled ? [0, 0, 255, 255] : [255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(result.subarray(0, 4)), "".concat(_mode, " clip outside")).to.deep.equal([255, 255, 255, 255]); + ctx.restore();case 12:_i14++;_context9.next = 10;break;case 13: + + + ctx.beginPath(); + ctx.arc(32, 32, 20, 0, Math.PI * 2); + ctx.fillStyle = "white"; + ctx.fillRect(0, 0, 64, 64); + ctx.strokeRect(0, 0, 2, 2); + ctx.clearRect(0, 0, 2, 2); + ctx.save(); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 64, 64); + ctx.restore();_context9.next = 14;return ( + read());case 14:preserved = _context9.sent; + outside = (8 * 64 + 8) * 4; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(preserved.subarray(outside, outside + 4)), "immediate operations preserve the curved current path").to.deep.equal([255, 255, 255, 255]); + center = (32 * 64 + 32) * 4; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(preserved.subarray(center, center + 4)), "preserved path interior").to.deep.equal([0, 0, 255, 255]);case 15:_context9.prev = 15; + + scene.dispose(); + engine.dispose();return _context9.finish(15);case 16:case "end":return _context9.stop();}}, _callee8, this, [[2,, 15, 16]]);})) + + ); + + (skipCanvasGpuTests ? it.skip : it)("clears only the clipped GPU region and ignores globalAlpha and filters", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee9() {var engine, scene, texture, ctx, _pixels3, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context0) {while (1) switch (_context0.prev = _context0.next) {case 0: + this.timeout(10000); + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context0.prev = 1; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("clipped clear", 64, scene, false); + ctx = texture.getContext(); + ctx.fillStyle = "red"; + ctx.fillRect(0, 0, 64, 64); + texture.update(false); + + ctx.filter = "blur(2px)"; + ctx.fillRect(0, 0, 64, 64); + ctx.save(); + ctx.beginPath(); + ctx.rect(8, 0, 24, 64); + ctx.clip(); + ctx.globalAlpha = 0.25; + ctx.clearRect(0, 0, 64, 64); + ctx.restore(); + texture.update(false);_context0.next = 2;return ( + texture.readPixels());case 2:_pixels3 = _context0.sent;if ( + _pixels3 instanceof Uint8Array) {_context0.next = 3;break;}throw ( + new Error("Expected RGBA8 GPU readback for the canvas texture"));case 3: + + pixel = function pixel(x) {return Array.from(_pixels3.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(36), "preserved near clip").to.deep.equal([255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(16), "fully cleared inside clip").to.deep.equal([0, 0, 0, 0]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(44), "preserved after clip").to.deep.equal([255, 0, 0, 255]);case 4:_context0.prev = 4; + + scene.dispose(); + engine.dispose();return _context0.finish(4);case 5:case "end":return _context0.stop();}}, _callee9, this, [[1,, 4, 5]]);})) + + ); + + (skipCanvasGpuTests ? it.skip : it)("uses the strokeRect geometry after a preceding fillRect", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee0() {var engine, scene, texture, ctx, _pixels4, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context1) {while (1) switch (_context1.prev = _context1.next) {case 0: + this.timeout(10000); + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context1.prev = 1; + + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("fill then inset stroke", 16, scene, false); + ctx = texture.getContext(); + ctx.fillStyle = "blue"; + ctx.fillRect(2, 2, 12, 12); + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + ctx.strokeRect(2.5, 2.5, 11, 11); + texture.update(false);_context1.next = 2;return ( + + texture.readPixels());case 2:_pixels4 = _context1.sent;if ( + _pixels4 instanceof Uint8Array) {_context1.next = 3;break;}throw ( + new Error("Expected RGBA8 GPU readback for the canvas texture"));case 3: + + pixel = function pixel(x, y) {return Array.from(_pixels4.subarray((y * 16 + x) * 4, (y * 16 + x + 1) * 4));}; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(8, 1), "outside inset border").to.deep.equal([0, 0, 0, 0]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(8, 2), "pixel-aligned inset border").to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(8, 3), "button fill inside border").to.deep.equal([0, 0, 255, 255]);case 4:_context1.prev = 4; + + scene.dispose(); + engine.dispose();return _context1.finish(4);case 5:case "end":return _context1.stop();}}, _callee0, this, [[1,, 4, 5]]);})) + + ); + + it("matches browser text layout metrics", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee1() {var fontData, ctx;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context10) {while (1) switch (_context10.prev = _context10.next) {case 0: + this.timeout(10000);_context10.next = 1;return ( + new Promise(function (resolve, reject) { + (0,_babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RequestFile)( + "app:///Assets/Arimo-Regular.ttf", + function (data) { + if (typeof data === "string") { + reject(new Error("Expected binary font data")); + } else { + resolve(data); + } + }, + undefined, + undefined, + true, + function (error) {return reject(error);} + ); + }));case 1:fontData = _context10.sent; + _native.Canvas.loadTTF("arimo-regular", fontData); + + ctx = createContext(); + ctx.font = "18px arimo-regular"; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Home Impulse").width).to.be.closeTo(116.0419921875, 0.0001); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Far Away Impulse").width).to.be.closeTo(143.71875, 0.0001); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Move far away").width).to.be.closeTo(117.0439453125, 0.0001); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Hg").fontBoundingBoxAscent).to.equal(16); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Hg").fontBoundingBoxDescent).to.equal(5); + ctx.letterSpacing = "0.25px"; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.measureText("Home Impulse").width).to.be.closeTo(118.7919921875, 0.0001);case 2:case "end":return _context10.stop();}}, _callee1, this);})) + ); function createCanvas(width, height) { var canvas = new _native.Canvas(); canvas.width = width; @@ -28864,15 +29624,86 @@ describe("Canvas2D", function () { } + itWithGpu("uses strokeRect geometry after a preceding fillRect", function () { + var resource = createCanvas(16, 16); + try { + var ctx = resource.context; + ctx.fillStyle = "blue"; + ctx.fillRect(2, 2, 12, 12); + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + ctx.strokeRect(2.5, 2.5, 11, 11); + + var _pixels5 = captureGpuPixels(resource.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels5, 16, 8, 1), "outside inset border"). + to.deep.equal([0, 0, 0, 0]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels5, 16, 8, 2), "pixel-aligned inset border"). + to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels5, 16, 8, 3), "fill inside border"). + to.deep.equal([0, 0, 255, 255]); + } finally { + disposeCanvas(resource); + } + }); + + itWithGpu("does not restroke earlier rectangles with a later strokeRect", function () { + var resource = createCanvas(32, 16); + try { + var ctx = resource.context; + ctx.lineWidth = 1; + ctx.strokeStyle = "red"; + ctx.strokeRect(2.5, 2.5, 9, 11); + ctx.strokeStyle = "blue"; + ctx.strokeRect(20.5, 2.5, 9, 11); + + var _pixels6 = captureGpuPixels(resource.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels6, 32, 2, 8), "first rectangle retains its stroke"). + to.deep.equal([255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels6, 32, 20, 8), "second rectangle is drawn"). + to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels6, 32, 16, 8), "gap remains untouched"). + to.deep.equal([0, 0, 0, 0]); + } finally { + disposeCanvas(resource); + } + }); + + itWithGpu("preserves a rectangular clip while resetting strokeRect geometry", function () { + var resource = createCanvas(32, 16); + try { + var ctx = resource.context; + ctx.save(); + ctx.beginPath(); + ctx.rect(0, 0, 16, 16); + ctx.clip(); + ctx.fillStyle = "blue"; + ctx.fillRect(0, 0, 32, 16); + ctx.lineWidth = 1; + ctx.strokeStyle = "white"; + ctx.strokeRect(2.5, 2.5, 27, 11); + ctx.restore(); + + var _pixels7 = captureGpuPixels(resource.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels7, 32, 2, 8), "stroke inside clip"). + to.deep.equal([255, 255, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels7, 32, 8, 8), "fill inside clip"). + to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels7, 32, 29, 8), "stroke outside clip"). + to.deep.equal([0, 0, 0, 0]); + } finally { + disposeCanvas(resource); + } + }); + (skipCanvasGpuTests ? it.skip : it)( "intersects nested clips and restores parent clips on the GPU", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark( - function _callee4() {var _loop, _i8, _arr8;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context5) {while (1) switch (_context5.prev = _context5.next) {case 0: - this.timeout(10000);_loop = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _loop() {var translated, engine, scene, texture, ctx, pixels, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context4) {while (1) switch (_context4.prev = _context4.next) {case 0: - translated = _arr8[_i8]; - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context4.prev = 1; + function _callee10() {var _loop3, _i15, _arr15;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context12) {while (1) switch (_context12.prev = _context12.next) {case 0: + this.timeout(10000);_loop3 = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _loop3() {var translated, engine, scene, texture, ctx, _pixels8, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context11) {while (1) switch (_context11.prev = _context11.next) {case 0: + translated = _arr15[_i15]; + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context11.prev = 1; - texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.DynamicTexture("nested clips", 64, scene, false); + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("nested clips", 64, scene, false); ctx = texture.getContext(); ctx.fillStyle = "white"; ctx.fillRect(0, 0, 64, 64); @@ -28905,14 +29736,14 @@ describe("Canvas2D", function () { ctx.fillStyle = "blue"; ctx.fillRect(40, 0, 8, 64); - texture.update(false);_context4.next = 2;return ( - texture.readPixels());case 2:pixels = _context4.sent;if ( - pixels instanceof Uint8Array) {_context4.next = 3;break;}throw ( + texture.update(false);_context11.next = 2;return ( + texture.readPixels());case 2:_pixels8 = _context11.sent;if ( + _pixels8 instanceof Uint8Array) {_context11.next = 3;break;}throw ( new Error("Expected RGBA8 GPU readback for the canvas texture"));case 3: pixel = function pixel(x) {return ( Array.from( - pixels.subarray( + _pixels8.subarray( (32 * 64 + x) * 4, (32 * 64 + x + 1) * 4 ) @@ -28940,10 +29771,10 @@ describe("Canvas2D", function () { ); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(60), "outside every fill").to.deep.equal([ 255, 255, 255, 255] - );case 4:_context4.prev = 4; + );case 4:_context11.prev = 4; scene.dispose(); - engine.dispose();return _context4.finish(4);case 5:case "end":return _context4.stop();}}, _loop, null, [[1,, 4, 5]]);});_i8 = 0, _arr8 = [false, true];case 1:if (!(_i8 < _arr8.length)) {_context5.next = 3;break;}return _context5.delegateYield(_loop(), "t0", 2);case 2:_i8++;_context5.next = 1;break;case 3:case "end":return _context5.stop();}}, _callee4, this);})) + engine.dispose();return _context11.finish(4);case 5:case "end":return _context11.stop();}}, _loop3, null, [[1,, 4, 5]]);});_i15 = 0, _arr15 = [false, true];case 1:if (!(_i15 < _arr15.length)) {_context12.next = 3;break;}return _context12.delegateYield(_loop3(), "t0", 2);case 2:_i15++;_context12.next = 1;break;case 3:case "end":return _context12.stop();}}, _callee10, this);})) @@ -28951,17 +29782,17 @@ describe("Canvas2D", function () { (skipCanvasGpuTests ? it.skip : it)( "normalizes negative rectangle dimensions before intersecting GPU clips", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark( - function _callee5() {var engine, scene, texture, ctx, _i9, _arr9, rotated, expected, _i0, _arr0, flips, flipX, flipY, _pixels, description, changed, index;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context6) {while (1) switch (_context6.prev = _context6.next) {case 0: + function _callee11() {var engine, scene, texture, ctx, _i16, _arr16, rotated, expected, _i17, _arr17, flips, flipX, flipY, _pixels9, description, changed, index;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context13) {while (1) switch (_context13.prev = _context13.next) {case 0: this.timeout(10000); - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context6.prev = 1; + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context13.prev = 1; - texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.DynamicTexture("signed clips", 64, scene, false); - ctx = texture.getContext();_i9 = 0, _arr9 = - [false, true];case 2:if (!(_i9 < _arr9.length)) {_context6.next = 8;break;}rotated = _arr9[_i9]; - expected = void 0;_i0 = 0, _arr0 = + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("signed clips", 64, scene, false); + ctx = texture.getContext();_i16 = 0, _arr16 = + [false, true];case 2:if (!(_i16 < _arr16.length)) {_context13.next = 8;break;}rotated = _arr16[_i16]; + expected = void 0;_i17 = 0, _arr17 = [ - [false, false], [true, false], [false, true], [true, true]];case 3:if (!(_i0 < _arr0.length)) {_context6.next = 7;break;}flips = _arr0[_i0]; + [false, false], [true, false], [false, true], [true, true]];case 3:if (!(_i17 < _arr17.length)) {_context13.next = 7;break;}flips = _arr17[_i17]; flipX = flips[0]; flipY = flips[1]; @@ -28987,47 +29818,47 @@ describe("Canvas2D", function () { ctx.fillRect(0, 0, 64, 64); ctx.restore(); ctx.restore(); - texture.update(false);_context6.next = 4;return ( + texture.update(false);_context13.next = 4;return ( - texture.readPixels());case 4:_pixels = _context6.sent;if ( - _pixels instanceof Uint8Array) {_context6.next = 5;break;}throw ( + texture.readPixels());case 4:_pixels9 = _context13.sent;if ( + _pixels9 instanceof Uint8Array) {_context13.next = 5;break;}throw ( new Error("Expected RGBA8 GPU readback for signed clips"));case 5: description = "rotated=".concat( rotated, ", flipX=").concat(flipX, ", flipY=").concat(flipY); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels, 64, 28, 30), "inside clip, ".concat(description)). + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels9, 64, 28, 30), "inside clip, ".concat(description)). to.deep.equal([0, 0, 255, 255]); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels, 64, 0, 0), "outside parent clip"). + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels9, 64, 0, 0), "outside parent clip"). to.deep.equal([255, 255, 255, 255]); if (expected) { changed = 0; - for (index = 0; index < _pixels.length; ++index) { - if (_pixels[index] !== expected[index]) { + for (index = 0; index < _pixels9.length; ++index) { + if (_pixels9[index] !== expected[index]) { ++changed; } } (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(changed, "signed clip equivalence, ".concat(description)). to.equal(0); } else { - expected = _pixels.slice(); - }case 6:_i0++;_context6.next = 3;break;case 7:_i9++;_context6.next = 2;break;case 8:_context6.prev = 8; + expected = _pixels9.slice(); + }case 6:_i17++;_context13.next = 3;break;case 7:_i16++;_context13.next = 2;break;case 8:_context13.prev = 8; scene.dispose(); - engine.dispose();return _context6.finish(8);case 9:case "end":return _context6.stop();}}, _callee5, this, [[1,, 8, 9]]);})) + engine.dispose();return _context13.finish(8);case 9:case "end":return _context13.stop();}}, _callee11, this, [[1,, 8, 9]]);})) ); (skipCanvasGpuTests ? it.skip : it)( "clears only the clipped GPU region and ignores globalAlpha and filters", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark( - function _callee6() {var engine, scene, texture, ctx, _pixels2, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context7) {while (1) switch (_context7.prev = _context7.next) {case 0: + function _callee12() {var engine, scene, texture, ctx, _pixels0, pixel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context14) {while (1) switch (_context14.prev = _context14.next) {case 0: this.timeout(10000); - engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine);_context7.prev = 1; + engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine);_context14.prev = 1; - texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.DynamicTexture("clipped clear", 64, scene, false); + texture = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DynamicTexture("clipped clear", 64, scene, false); ctx = texture.getContext(); ctx.fillStyle = "red"; ctx.fillRect(0, 0, 64, 64); @@ -29042,14 +29873,14 @@ describe("Canvas2D", function () { ctx.globalAlpha = 0.25; ctx.clearRect(0, 0, 64, 64); ctx.restore(); - texture.update(false);_context7.next = 2;return ( - texture.readPixels());case 2:_pixels2 = _context7.sent;if ( - _pixels2 instanceof Uint8Array) {_context7.next = 3;break;}throw ( + texture.update(false);_context14.next = 2;return ( + texture.readPixels());case 2:_pixels0 = _context14.sent;if ( + _pixels0 instanceof Uint8Array) {_context14.next = 3;break;}throw ( new Error("Expected RGBA8 GPU readback for the canvas texture"));case 3: pixel = function pixel(x) {return ( Array.from( - _pixels2.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4) + _pixels0.subarray((32 * 64 + x) * 4, (32 * 64 + x + 1) * 4) ));}; (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(36), "preserved near clip").to.deep.equal([ 255, 0, 0, 255] @@ -29059,10 +29890,10 @@ describe("Canvas2D", function () { ); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixel(44), "preserved after clip").to.deep.equal([ 255, 0, 0, 255] - );case 4:_context7.prev = 4; + );case 4:_context14.prev = 4; scene.dispose(); - engine.dispose();return _context7.finish(4);case 5:case "end":return _context7.stop();}}, _callee6, this, [[1,, 4, 5]]);})) + engine.dispose();return _context14.finish(4);case 5:case "end":return _context14.stop();}}, _callee12, this, [[1,, 4, 5]]);})) ); @@ -29075,6 +29906,115 @@ describe("Canvas2D", function () { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.strokeStyle).to.equal("#00ff00"); }); + it("rejects a prototype-spoofed object in place of a Path2D", function () { + // Prototype spoofing must not let fill() unwrap a foreign or nonexistent native object. + var ctx = createContext(); + var spoofedGradient = ctx.createLinearGradient(0, 0, 10, 10); + Object.setPrototypeOf(spoofedGradient, Path2D.prototype); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(spoofedGradient instanceof Path2D).to.equal(true); + + var bare = Object.create(Path2D.prototype); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(bare instanceof Path2D).to.equal(true); + + var realPath = new Path2D();var _loop4 = function _loop4() + {var impostor = _arr18[_i18]; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(impostor);}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke(impostor);}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {realPath.addPath(impostor);}).to.throw(); + // The Path2D() argument is a (Path2D or DOMString) union, so a non-Path2D is string + // data rather than an error. It must not be unwrapped on the way there. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {new Path2D(impostor);}).to.not.throw(); + };for (var _i18 = 0, _arr18 = [spoofedGradient, bare]; _i18 < _arr18.length; _i18++) {_loop4();} + }); + + it("ignores a prototype-spoofed object assigned to fillStyle or strokeStyle", function () { + // Same defect on the gradient side: the assignment gate accepted anything wearing the + // gradient prototype and stored it, and the next fill unwrapped it as a CanvasGradient. + var ctx = createContext(); + var gradient = ctx.createLinearGradient(0, 0, 10, 10); + var spoofedPath = new Path2D(); + Object.setPrototypeOf(spoofedPath, Object.getPrototypeOf(gradient)); + + ctx.fillStyle = "#ff0000"; + ctx.strokeStyle = "#00ff00"; + ctx.fillStyle = spoofedPath; + ctx.strokeStyle = spoofedPath; + + // Per spec an unusable assignment leaves the previous value in place. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.fillStyle).to.equal("#ff0000"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(ctx.strokeStyle).to.equal("#00ff00"); + + // The drawing path must stay usable rather than unwrapping the impostor. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fillRect(0, 0, 10, 10);}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.strokeRect(0, 0, 10, 10);}).to.not.throw(); + }); + + it("rejects a non-Path2D argument to fill and stroke", function () { + // Assert rejection, not the exception class: QuickJS reports native TypeErrors as InternalErrors. + var ctx = createContext(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke("x");}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke({});}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke(5);}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill({});}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(5);}).to.throw(); + }); + + it("still accepts the valid fill and stroke argument forms", function () { + var ctx = createContext(); + var path = new Path2D("M0 0 L10 10"); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill();}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke();}).to.not.throw(); + // undefined selects the no-argument overload rather than being a bad Path2D. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(undefined);}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke(undefined);}).to.not.throw(); + // fill() also takes a fill rule string. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill("evenodd");}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(new String("evenodd"));}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () { + ctx.fill({ toString: function toString() {return "nonzero";} }); + }).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(path);}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(path, "nonzero");}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(path, new String("evenodd"));}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.stroke(path);}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill("invalid");}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(path, "invalid");}).to.throw(); + }); + + it("rejects a non-Path2D argument to Path2D.addPath", function () { + // Missing and unrelated arguments must not reach native unwrapping. + var path = new Path2D(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {path.addPath();}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {path.addPath("x");}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {path.addPath({});}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {path.addPath(new Path2D("M0 0 L5 5"));}).to.not.throw(); + }); + + it("rejects an invalid native source argument to drawImage", function () { + // Native-looking impostors must throw, not AV via an unchecked Unwrap. + var ctx = createContext(); + var realCanvas = new _native.Canvas(); + var spoofedCanvas = Object.create(Object.getPrototypeOf(realCanvas)); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(spoofedCanvas instanceof _native.Canvas).to.equal(true); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.drawImage({}, 0, 0);}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.drawImage(new Path2D(), 0, 0);}).to.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.drawImage(spoofedCanvas, 0, 0);}).to.throw(); + realCanvas.dispose(); + }); + + it("treats a non-Path2D Path2D() argument as path data", function () { + // Non-Path2D objects use the DOMString constructor overload. + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {new Path2D({});}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {new Path2D(5);}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {new Path2D();}).to.not.throw(); + var source = new Path2D("M0 0 L10 10"); + var ctx = createContext(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {ctx.fill(new Path2D(source));}).to.not.throw(); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () { + ctx.fill(new Path2D({ toString: function toString() {return "M0 0 L10 10";} })); + }).to.not.throw(); + }); + it("accepts a CanvasGradient as fillStyle", function () { var ctx = createContext(); var gradient = ctx.createLinearGradient(0, 0, 64, 64); @@ -29499,14 +30439,14 @@ describe("Canvas2D", function () { 8.5 ); - var _pixels3 = captureGpuPixels(destination.canvas); + var _pixels1 = captureGpuPixels(destination.canvas); // Pixel (12,3), sampled at its center (12.5,3.5), maps to source // x = 5.99 + (12.5 - 2.75) * 1.01 / 24.5 = 6.39194. // Between red texel center 5.5 and green center 6.5, ideal bilinear // weights are 10.8% red / 89.2% green (R~28, G~227). // Integer truncation maps x to 5.4375; ignoring the crop maps x to // 5.25. Both negative controls are solid red at this sample. - var green = pixelAt(_pixels3, destination.canvas.width, 12, 3); + var green = pixelAt(_pixels1, destination.canvas.width, 12, 3); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(green[1] - green[0]).to.be.greaterThan(100); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(green[3]).to.be.greaterThan(200); @@ -29515,12 +30455,12 @@ describe("Canvas2D", function () { // That is 18.3% top red / 81.7% bottom blue (R~47, B~208). // Integer truncation maps y to 3.4375 and ignoring the crop maps y // to 3.5, both solid red rather than blue. - var blue = pixelAt(_pixels3, destination.canvas.width, 3, 14); + var blue = pixelAt(_pixels1, destination.canvas.width, 3, 14); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(blue[2] - blue[0]).to.be.greaterThan(80); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(blue[3]).to.be.greaterThan(200); var fractionalEdge = pixelAt( - _pixels3, + _pixels1, destination.canvas.width, 26, 3 @@ -29556,14 +30496,14 @@ describe("Canvas2D", function () { -6 ); - var _pixels4 = captureGpuPixels(destination.canvas); - var inside = pixelAt(_pixels4, destination.canvas.width, 7, 7); + var _pixels10 = captureGpuPixels(destination.canvas); + var inside = pixelAt(_pixels10, destination.canvas.width, 7, 7); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[0]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[1]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[2]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[3]).to.be.greaterThan(220); - var outside = pixelAt(_pixels4, destination.canvas.width, 2, 7); + var outside = pixelAt(_pixels10, destination.canvas.width, 2, 7); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(outside[3]).to.equal(0); } finally { disposeCanvas(destination); @@ -29589,10 +30529,10 @@ describe("Canvas2D", function () { 12 ); - var _pixels5 = captureGpuPixels(destination.canvas); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels5, destination.canvas.width, 5, 5)[3]).to.equal(0); + var _pixels11 = captureGpuPixels(destination.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels11, destination.canvas.width, 5, 5)[3]).to.equal(0); - var inside = pixelAt(_pixels5, destination.canvas.width, 10, 10); + var inside = pixelAt(_pixels11, destination.canvas.width, 10, 10); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[0]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[1]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(inside[2]).to.be.greaterThan(220); @@ -29615,14 +30555,14 @@ describe("Canvas2D", function () { source.context.fillRect(0, 0, 8, 8); destination.context.drawImage(source.canvas, 8, 0, 8, 8); - var _pixels6 = captureGpuPixels(destination.canvas); - var first = pixelAt(_pixels6, destination.canvas.width, 4, 4); + var _pixels12 = captureGpuPixels(destination.canvas); + var first = pixelAt(_pixels12, destination.canvas.width, 4, 4); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(first[0]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(first[1]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(first[2]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(first[3]).to.be.greaterThan(220); - var second = pixelAt(_pixels6, destination.canvas.width, 12, 4); + var second = pixelAt(_pixels12, destination.canvas.width, 12, 4); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(second[0]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(second[1]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(second[2]).to.be.greaterThan(220); @@ -29663,14 +30603,14 @@ describe("Canvas2D", function () { 1 ); - var _pixels7 = captureGpuPixels(destination.canvas); - var putSample = pixelAt(_pixels7, destination.canvas.width, 2, 2); + var _pixels13 = captureGpuPixels(destination.canvas); + var putSample = pixelAt(_pixels13, destination.canvas.width, 2, 2); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(putSample[0]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(putSample[1]).to.be.within(75, 115); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(putSample[2]).to.be.within(15, 50); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(putSample[3]).to.be.greaterThan(240); - var drawSample = pixelAt(_pixels7, destination.canvas.width, 8, 2); + var drawSample = pixelAt(_pixels13, destination.canvas.width, 8, 2); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(drawSample[0]).to.be.within(75, 115); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(drawSample[1]).to.be.within(30, 70); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(drawSample[2]).to.be.greaterThan(220); @@ -29711,15 +30651,15 @@ describe("Canvas2D", function () { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(transform.e).to.equal(2); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(transform.f).to.equal(1); - var _pixels8 = captureGpuPixels(destination.canvas); - var cropped = pixelAt(_pixels8, destination.canvas.width, 5, 4); + var _pixels14 = captureGpuPixels(destination.canvas); + var cropped = pixelAt(_pixels14, destination.canvas.width, 5, 4); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(cropped[0]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(cropped[1]).to.be.within(100, 155); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(cropped[2]).to.be.within(100, 155); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(cropped[3]).to.be.greaterThan(240); var translatedEdge = pixelAt( - _pixels8, + _pixels14, destination.canvas.width, 8, 4 @@ -29729,7 +30669,7 @@ describe("Canvas2D", function () { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(translatedEdge[2]).to.be.within(100, 155); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(translatedEdge[3]).to.be.greaterThan(240); - var clippedOut = pixelAt(_pixels8, destination.canvas.width, 5, 8); + var clippedOut = pixelAt(_pixels14, destination.canvas.width, 5, 8); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(clippedOut[0]).to.be.lessThan(10); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(clippedOut[1]).to.be.lessThan(10); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(clippedOut[2]).to.be.lessThan(10); @@ -29758,8 +30698,8 @@ describe("Canvas2D", function () { destination.context.drawImage(source.canvas, 2, 2, 4, 4); destination.context.drawImage(source.canvas, 10, 8, 4, 4); - var _pixels9 = captureGpuPixels(destination.canvas); - var firstDraw = pixelAt(_pixels9, destination.canvas.width, 3, 3); + var _pixels15 = captureGpuPixels(destination.canvas); + var firstDraw = pixelAt(_pixels15, destination.canvas.width, 3, 3); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(firstDraw[0]).to.be.greaterThan(220); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(firstDraw[1]).to.be.lessThan(30); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(firstDraw[2]).to.be.lessThan(30); @@ -29767,9 +30707,9 @@ describe("Canvas2D", function () { [ // Inside the triangle but outside both destination rectangles. - pixelAt(_pixels9, destination.canvas.width, 2, 8), + pixelAt(_pixels15, destination.canvas.width, 2, 8), // Inside the second destination rectangle but outside the triangle. - pixelAt(_pixels9, destination.canvas.width, 12, 9)]. + pixelAt(_pixels15, destination.canvas.width, 12, 9)]. forEach(function (outside) { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(outside[0]).to.be.lessThan(10); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(outside[1]).to.be.lessThan(10); @@ -29790,13 +30730,13 @@ describe("Canvas2D", function () { source.context.fillRect(0, 0, 2, 2); destination.context.drawImage(source.canvas, 1.75, 1.75, 2.5, 2.5); - var _pixels0 = destination.context.getImageData(0, 0, 8, 8).data; - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels0, destination.canvas.width, 2, 2)[3]).to.equal(255); + var _pixels16 = destination.context.getImageData(0, 0, 8, 8).data; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels16, destination.canvas.width, 2, 2)[3]).to.equal(255); [ - pixelAt(_pixels0, destination.canvas.width, 1, 2), - pixelAt(_pixels0, destination.canvas.width, 4, 2), - pixelAt(_pixels0, destination.canvas.width, 2, 1), - pixelAt(_pixels0, destination.canvas.width, 2, 4)]. + pixelAt(_pixels16, destination.canvas.width, 1, 2), + pixelAt(_pixels16, destination.canvas.width, 4, 2), + pixelAt(_pixels16, destination.canvas.width, 2, 1), + pixelAt(_pixels16, destination.canvas.width, 2, 4)]. forEach(function (edge) { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(edge[3]).to.be.within(40, 90); }); @@ -29816,9 +30756,9 @@ describe("Canvas2D", function () { resource.context.fillStyle = "#0000ff"; resource.context.fillRect(0, 0, 4, 8); - var _pixels1 = captureGpuPixels(resource.canvas); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels1, 8, 2, 2)).to.deep.equal([0, 0, 255, 255]); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels1, 8, 6, 6)).to.deep.equal([255, 0, 0, 255]); + var _pixels17 = captureGpuPixels(resource.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels17, 8, 2, 2)).to.deep.equal([0, 0, 255, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels17, 8, 6, 6)).to.deep.equal([255, 0, 0, 255]); } finally { disposeCanvas(resource); } @@ -29832,9 +30772,9 @@ describe("Canvas2D", function () { source.context.fillRect(4, 4, 8, 8); destination.context.filter = "blur(2px)"; destination.context.drawImage(source.canvas, 0, 0); - var _pixels10 = captureGpuPixels(destination.canvas); - var fringe = pixelAt(_pixels10, 16, 3, 8); - var center = pixelAt(_pixels10, 16, 8, 8); + var _pixels18 = captureGpuPixels(destination.canvas); + var fringe = pixelAt(_pixels18, 16, 3, 8); + var center = pixelAt(_pixels18, 16, 8, 8); // The unfiltered CPU copy has zero alpha outside the source ink. (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(fringe[0]).to.equal(fringe[1]); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(fringe[1]).to.equal(fringe[2]); @@ -29861,9 +30801,9 @@ describe("Canvas2D", function () { } }, 0); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(conversions).to.equal(1); - var _pixels11 = captureGpuPixels(destination.canvas); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels11, 8, 2, 2)).to.deep.equal([255, 0, 0, 255]); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels11, 8, 6, 2)).to.deep.equal([0, 0, 0, 0]); + var _pixels19 = captureGpuPixels(destination.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels19, 8, 2, 2)).to.deep.equal([255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels19, 8, 6, 2)).to.deep.equal([0, 0, 0, 0]); } finally { disposeCanvas(destination); disposeCanvas(source); @@ -29888,9 +30828,9 @@ describe("Canvas2D", function () { } }, 0); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(conversions).to.equal(1); - var _pixels12 = captureGpuPixels(destination.canvas); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels12, 8, 2, 2)).to.deep.equal([255, 0, 0, 255]); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels12, 8, 6, 2)).to.deep.equal([0, 0, 0, 0]); + var _pixels20 = captureGpuPixels(destination.canvas); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels20, 8, 2, 2)).to.deep.equal([255, 0, 0, 255]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pixelAt(_pixels20, 8, 6, 2)).to.deep.equal([0, 0, 0, 0]); } finally { Object.setPrototypeOf(source.canvas, prototype); disposeCanvas(destination); @@ -29977,7 +30917,7 @@ describe("Canvas2D", function () { canvas.height = size; try { var url = canvas.toDataURL(); - var png = buffer__WEBPACK_IMPORTED_MODULE_5__.Buffer.from(url.substring("data:image/png;base64,".length), "base64"); + var png = buffer__WEBPACK_IMPORTED_MODULE_7__.Buffer.from(url.substring("data:image/png;base64,".length), "base64"); (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(Array.from(png.subarray(0, 8))).to.deep.equal([137, 80, 78, 71, 13, 10, 26, 10]); var offset = 8; var foundEnd = false; @@ -30000,24 +30940,11 @@ describe("Canvas2D", function () { }); }); - it("returns ascent and descent in no-font text metrics", function () { - var resource = createCanvas(8, 8); - try { - resource.context.font = "20px MissingFontForCanvasMetrics"; - var metrics = resource.context.measureText("test"); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics).to.have.property("actualBoundingBoxAscent"); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics).to.have.property("actualBoundingBoxDescent"); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics.actualBoundingBoxAscent).to.equal(15); - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(metrics.actualBoundingBoxDescent).to.equal(5); - } finally { - disposeCanvas(resource); - } - }); }); function createSceneAndWait(callback, done) { - var engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.NativeEngine(); - var scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Scene(engine); + var engine = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.NativeEngine(); + var scene = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Scene(engine); scene.createDefaultCamera(); callback(engine, scene); scene.executeWhenReady(function () { @@ -30025,12 +30952,38 @@ function createSceneAndWait(callback, done) { }); } +describe("NativeTextureFormats", function () { + it("creates a D24 render target with compatible backing storage", function () { + var engine = new _native.Engine(); + var texture = engine.createTexture(); + try { + engine.initializeTexture(texture, 16, 16, false, _native.Engine.TEXTURE_FORMAT_D24, true, false, 1); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(engine.getTextureWidth(texture)).to.equal(16); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(engine.getTextureHeight(texture)).to.equal(16); + } finally { + engine.deleteTexture(texture); + engine.dispose(); + } + }); + + it("rejects an invalid format without entering bgfx texture creation", function () { + var engine = new _native.Engine(); + var texture = engine.createTexture(); + try { + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(function () {return engine.initializeTexture(texture, 16, 16, false, 0xffffffff, true, false, 1);}).to.throw("Invalid texture format"); + } finally { + engine.deleteTexture(texture); + engine.dispose(); + } + }); +}); + describe("Materials", function () { this.timeout(0); it("Empty ShaderMaterial should compile", function (done) { function createEmptyShaderMat() { createSceneAndWait(function (engine, scene) { - var sphere = _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.MeshBuilder.CreateSphere( + var sphere = _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.MeshBuilder.CreateSphere( "sphere", { diameter: 2, segments: 32 }, scene @@ -30039,7 +30992,7 @@ describe("Materials", function () { vertexSource: "void main() {}", fragmentSource: "void main() {}" }; - var mat = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.ShaderMaterial("shader", scene, shaders, {}); + var mat = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.ShaderMaterial("shader", scene, shaders, {}); sphere.material = mat; }, done); } @@ -30047,12 +31000,12 @@ describe("Materials", function () { }); it("GradientMaterial should compile", function (done) { createSceneAndWait(function (engine, scene) { - var sphere = _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.MeshBuilder.CreateSphere( + var sphere = _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.MeshBuilder.CreateSphere( "sphere", { diameter: 2, segments: 32 }, scene ); - var gradientMaterial = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.GradientMaterial("grad", scene); + var gradientMaterial = new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.GradientMaterial("grad", scene); sphere.material = gradientMaterial; }, done); }); @@ -30063,21 +31016,21 @@ describe("PostProcesses", function () { it("PassPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.PassPostProcess("Scene copy", 1.0, camera); + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.PassPostProcess("Scene copy", 1.0, camera); }, done); }); it("BlackAndWhitePostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.BlackAndWhitePostProcess("bandw", 1.0, camera); + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.BlackAndWhitePostProcess("bandw", 1.0, camera); }, done); }); it("BlurPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.BlurPostProcess( + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.BlurPostProcess( "Horizontal blur", - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Vector2(1.0, 0), + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Vector2(1.0, 0), 32, 0.25, camera @@ -30087,9 +31040,9 @@ describe("PostProcesses", function () { it("ConvolutionPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.ConvolutionPostProcess( + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.ConvolutionPostProcess( "Sepia", - _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.ConvolutionPostProcess.EmbossKernel, + _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.ConvolutionPostProcess.EmbossKernel, 1.0, camera ); @@ -30098,28 +31051,28 @@ describe("PostProcesses", function () { it("HighlightsPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.HighlightsPostProcess("highlights", 1.0, camera); + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.HighlightsPostProcess("highlights", 1.0, camera); }, done); }); it("TonemapPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.TonemapPostProcess("tonemap", _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.TonemappingOperator.Hable, 1.0, camera); + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.TonemapPostProcess("tonemap", _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.TonemappingOperator.Hable, 1.0, camera); }, done); }); it("ImageProcessingPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.ImageProcessingPostProcess("processing", 1.0, camera); + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.ImageProcessingPostProcess("processing", 1.0, camera); }, done); }); it("RefractionPostProcess", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.RefractionPostProcess( + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.RefractionPostProcess( "Refraction", "https://playground.babylonjs.com/textures/grass.jpg", - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.Color3(1.0, 1.0, 1.0), + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.Color3(1.0, 1.0, 1.0), 0.5, 0.5, 1.0, @@ -30130,7 +31083,7 @@ describe("PostProcesses", function () { it("DefaultPipeline", function (done) { createSceneAndWait(function (engine, scene) { var camera = scene._activeCamera; - new _babylonjs_core__WEBPACK_IMPORTED_MODULE_6__.DefaultRenderingPipeline( + new _babylonjs_core__WEBPACK_IMPORTED_MODULE_8__.DefaultRenderingPipeline( "defaultPipeline", // The name of the pipeline true, // Do you want the pipeline to use HDR texture? scene, // The scene instance @@ -30184,9 +31137,9 @@ describe("PostProcesses", function () { describe("NativeEncoding", function () { this.timeout(0);function - expectValidPNG(_x4) {return _expectValidPNG.apply(this, arguments);}function _expectValidPNG() {_expectValidPNG = (0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee9(blob) {var arrayBuffer, pngSignature;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context0) {while (1) switch (_context0.prev = _context0.next) {case 0: - (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(blob).to.be.instanceOf(Blob);_context0.next = 1;return ( - blob.arrayBuffer());case 1:arrayBuffer = _context0.sent; + expectValidPNG(_x4) {return _expectValidPNG.apply(this, arguments);}function _expectValidPNG() {_expectValidPNG = (0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee15(blob) {var arrayBuffer, pngSignature;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context17) {while (1) switch (_context17.prev = _context17.next) {case 0: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(blob).to.be.instanceOf(Blob);_context17.next = 1;return ( + blob.arrayBuffer());case 1:arrayBuffer = _context17.sent; (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(arrayBuffer.byteLength).to.be.greaterThan(0); pngSignature = new Uint8Array(arrayBuffer.slice(0, 4)); @@ -30194,23 +31147,23 @@ describe("NativeEncoding", function () { (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pngSignature[1]).to.equal(80); // 'P' (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pngSignature[2]).to.equal(78); // 'N' (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(pngSignature[3]).to.equal(71); // 'G' - case 2:case "end":return _context0.stop();}}, _callee9);}));return _expectValidPNG.apply(this, arguments);} + case 2:case "end":return _context17.stop();}}, _callee15);}));return _expectValidPNG.apply(this, arguments);} - it("should encode a PNG", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee7() {var pixelData, result;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context8) {while (1) switch (_context8.prev = _context8.next) {case 0: - pixelData = new Uint8Array(4).fill(255);_context8.next = 1;return ( - _native.EncodeImageAsync(pixelData, 1, 1, "image/png", false));case 1:result = _context8.sent;_context8.next = 2;return ( - expectValidPNG(result));case 2:case "end":return _context8.stop();}}, _callee7);})) + it("should encode a PNG", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee13() {var pixelData, result;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context15) {while (1) switch (_context15.prev = _context15.next) {case 0: + pixelData = new Uint8Array(4).fill(255);_context15.next = 1;return ( + _native.EncodeImageAsync(pixelData, 1, 1, "image/png", false));case 1:result = _context15.sent;_context15.next = 2;return ( + expectValidPNG(result));case 2:case "end":return _context15.stop();}}, _callee13);})) ); - it("should handle multiple concurrent encoding tasks", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee8() {var pixelDatas, i, results;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context9) {while (1) switch (_context9.prev = _context9.next) {case 0: + it("should handle multiple concurrent encoding tasks", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee14() {var pixelDatas, i, results;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context16) {while (1) switch (_context16.prev = _context16.next) {case 0: pixelDatas = []; for (i = 0; i < 10; i++) { pixelDatas.push(new Uint8Array(4).fill(255)); - }_context9.next = 1;return ( + }_context16.next = 1;return ( Promise.all(pixelDatas.map(function (pixelData) {return ( _native.EncodeImageAsync(pixelData, 1, 1, "image/png", false));} - )));case 1:results = _context9.sent;_context9.next = 2;return ( - Promise.all(results.map(function (b) {return expectValidPNG(b);})));case 2:case "end":return _context9.stop();}}, _callee8);})) + )));case 1:results = _context16.sent;_context16.next = 2;return ( + Promise.all(results.map(function (b) {return expectValidPNG(b);})));case 2:case "end":return _context16.stop();}}, _callee14);})) ); }); @@ -30631,145 +31584,135 @@ function hexToBytes(hex) { describe("Canvas image reloads", function () { this.timeout(5000); var test = hasNativeImageLoading ? it : it.skip; - var pixelTest = hasNativeImageLoading && hasGpuRendering && !skipCanvasGpuTests ? it : it.skip; var url = "app:///Assets/image-reload.png"; - var dataUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAADElEQVR42mNgqP8PAAIBAX+LG2RhAAAAAElFTkSuQmCC"; + var dataUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAABCAAAAAA+i0toAAAADElEQVR42mNgqP8PAAIBAX+LG2RhAAAAAElFTkSuQmCC";var _loop5 = function _loop5() + + {var sources = _arr19[_i19]; + test("loads the same image again (".concat(sources.map(function (source) {return source === url ? "URL" : "data";}).join(" to "), ")"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee16() {var image, _iterator2, _step2, _loop6, _t4;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context19) {while (1) switch (_context19.prev = _context19.next) {case 0: + image = new _native.Image();_iterator2 = _createForOfIteratorHelper( + sources);_context19.prev = 1;_loop6 = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _loop6() {var source;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context18) {while (1) switch (_context18.prev = _context18.next) {case 0:source = _step2.value;_context18.next = 1;return ( + new Promise(function (resolve, reject) { + image.onload = resolve; + image.onerror = reject; + image.src = source; + }));case 1: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.width).to.equal(source === url ? 2 : 3); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.naturalWidth).to.equal(image.width); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.height).to.equal(1); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.naturalHeight).to.equal(1);case 2:case "end":return _context18.stop();}}, _loop6);});_iterator2.s();case 2:if ((_step2 = _iterator2.n()).done) {_context19.next = 4;break;}return _context19.delegateYield(_loop6(), "t0", 3);case 3:_context19.next = 2;break;case 4:_context19.next = 6;break;case 5:_context19.prev = 5;_t4 = _context19["catch"](1);_iterator2.e(_t4);case 6:_context19.prev = 6;_iterator2.f();return _context19.finish(6);case 7:case "end":return _context19.stop();}}, _callee16, null, [[1, 5, 6, 7]]);})) + + ); + };for (var _i19 = 0, _arr19 = [[url, url], [url, dataUrl], [dataUrl, url], [dataUrl, dataUrl]]; _i19 < _arr19.length; _i19++) {_loop5();} + + test("reflects the assigned src immediately", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee17() {var image;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context20) {while (1) switch (_context20.prev = _context20.next) {case 0: + image = new _native.Image();_context20.next = 1;return ( + new Promise(function (resolve, reject) { + image.onload = resolve; + image.onerror = reject; + image.src = dataUrl; + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.src).to.equal(dataUrl); + }));case 1: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.src).to.equal(dataUrl);case 2:case "end":return _context20.stop();}}, _callee17);})) + );var _loop7 = function _loop7() + + {var fromUrl = _arr20[_i20]; + test("only delivers the latest assignment after a pending ".concat(fromUrl ? "URL" : "data", " load"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee18() {var image, barrier, loaded, errors;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context21) {while (1) switch (_context21.prev = _context21.next) {case 0: + setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_7__.Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4nGP4z8AAQv8BD/kD/YURmXYAAAAASUVORK5CYII=", "base64")); + image = new _native.Image(); + barrier = new _native.Image(); + loaded = []; + errors = [];_context21.next = 1;return ( + new Promise(function (resolve, reject) { + image.onload = function () {loaded.push(image.width);}; + image.onerror = function (error) {errors.push(error);reject(error);}; + barrier.onload = resolve; + barrier.onerror = reject; + // The in-memory URL resolver queues completion synchronously. The final data load + // is a runtime-queue barrier after both candidate callbacks, not a timing estimate. + image.src = fromUrl ? "image-reload-test:///image.png" : dataUrl; + image.src = dataUrl; + barrier.src = dataUrl; + }));case 1: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.src).to.equal(dataUrl); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(loaded).to.deep.equal([3]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(errors).to.deep.equal([]); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.width).to.equal(3); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(barrier.width).to.equal(3);case 2:case "end":return _context21.stop();}}, _callee18);})) + ); + };for (var _i20 = 0, _arr20 = [true, false]; _i20 < _arr20.length; _i20++) {_loop7();} + + var pixelTest = hasNativeImageLoading && hasGpuRendering && !skipCanvasGpuTests ? it : it.skip; var redPng = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR42mP4z8DwH4QBEfcD/f6tu5kAAAAASUVORK5CYII="; - var greenPng = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mNg+A+BAA/5A/3mxKLtAAAAAElFTkSuQmCC"; - var expect = chai__WEBPACK_IMPORTED_MODULE_4__.expect; - - [[url, url], [url, dataUrl], [dataUrl, url], [dataUrl, dataUrl]].forEach(function (sources) { - test("loads the same image again (" + sources.map(function (source) { - return source === url ? "URL" : "data"; - }).join(" to ") + ")", function () { - var image = new _native.Image(); - return sources.reduce(function (pending, source) { - return pending.then(function () { - return new Promise(function (resolve, reject) { - image.onload = resolve; - image.onerror = reject; - image.src = source; - }).then(function () { - expect(image.width).to.equal(source === url ? 2 : 3); - expect(image.naturalWidth).to.equal(image.width); - expect(image.height).to.equal(1); - expect(image.naturalHeight).to.equal(1); - }); - }); - }, Promise.resolve()); - }); - }); + var greenPng = "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADElEQVR42mNg+A+BAA/5A/3mxKLtAAAAAElFTkSuQmCC";var _loop8 = function _loop8() + {var toData = _arr21[_i21]; + pixelTest("draws reloaded pixels instead of the cached texture (URL to ".concat(toData ? "data" : "URL", ")"), /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee19() {var canvas, context, image, load, sample, expectChannel;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context22) {while (1) switch (_context22.prev = _context22.next) {case 0: + canvas = new _native.Canvas(); + canvas.width = 4; + canvas.height = 4; + context = canvas.getContext("2d");_context22.prev = 1; + + image = new _native.Image(); + load = function load(assign) {return new Promise(function (resolve, reject) { + image.onload = resolve; + image.onerror = reject; + assign(); + });}; + sample = function sample() {return context.getImageData(1, 1, 1, 1).data;}; + expectChannel = function expectChannel(data, channel) { + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(data[channel]).to.be.greaterThan(240); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(data[1 - channel]).to.be.lessThan(20); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(data[2]).to.be.lessThan(20); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(data[3]).to.be.greaterThan(240); + }; - test("reflects the assigned src immediately", function () { - var image = new _native.Image(); - return new Promise(function (resolve, reject) { - image.onload = resolve; - image.onerror = reject; - image.src = dataUrl; - expect(image.src).to.equal(dataUrl); - }).then(function () { - expect(image.src).to.equal(dataUrl); - }); - }); + setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_7__.Buffer.from(redPng, "base64"));_context22.next = 2;return ( + load(function () { + image.src = "image-reload-test:///red.png"; + }));case 2: + context.drawImage(image, 0, 0, 4, 4); + expectChannel(sample(), 0);if (! - [true, false].forEach(function (fromUrl) { - test("only delivers the latest assignment after a pending " + (fromUrl ? "URL" : "data") + " load", function () { - setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_5__.Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAADklEQVR4nGP4z8AAQv8BD/kD/YURmXYAAAAASUVORK5CYII=", "base64")); - var image = new _native.Image(); - var barrier = new _native.Image(); - var loaded = []; - var errors = []; - return new Promise(function (resolve, reject) { - image.onload = function () { loaded.push(image.width); }; - image.onerror = function (error) { errors.push(error); reject(error); }; - barrier.onload = resolve; - barrier.onerror = reject; - image.src = fromUrl ? "image-reload-test:///image.png" : dataUrl; - image.src = dataUrl; - barrier.src = dataUrl; - }).then(function () { - expect(image.src).to.equal(dataUrl); - expect(loaded).to.deep.equal([3]); - expect(errors).to.deep.equal([]); - expect(image.width).to.equal(3); - expect(barrier.width).to.equal(3); - }); - }); - }); + toData) {_context22.next = 4;break;}_context22.next = 3;return ( + load(function () { + image.src = "data:image/png;base64," + greenPng; + }));case 3:_context22.next = 5;break;case 4: - [false, true].forEach(function (toData) { - pixelTest("draws reloaded pixels instead of the cached texture (URL to " + (toData ? "data" : "URL") + ")", function () { - var canvas = new _native.Canvas(); - canvas.width = 4; - canvas.height = 4; - var context = canvas.getContext("2d"); - var image = new _native.Image(); - function load(assign) { - return new Promise(function (resolve, reject) { - image.onload = resolve; - image.onerror = reject; - assign(); - }); - } - function expectChannel(channel) { - var data = context.getImageData(1, 1, 1, 1).data; - expect(data[channel]).to.be.greaterThan(240); - expect(data[1 - channel]).to.be.lessThan(20); - expect(data[2]).to.be.lessThan(20); - expect(data[3]).to.be.greaterThan(240); - } - return load(function () { - setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_5__.Buffer.from(redPng, "base64")); - image.src = "image-reload-test:///red.png"; - }).then(function () { - context.drawImage(image, 0, 0, 4, 4); - expectChannel(0); - return load(function () { - if (toData) { - image.src = "data:image/png;base64," + greenPng; - } else { - setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_5__.Buffer.from(greenPng, "base64")); - image.src = "image-reload-test:///green.png"; - } - }); - }).then(function () { - context.clearRect(0, 0, 4, 4); - context.drawImage(image, 0, 0, 4, 4); - expectChannel(1); - context.dispose(); - canvas.dispose(); - }, function (error) { - context.dispose(); - canvas.dispose(); - throw error; - }); - }); - }); + setImageReloadTestResponse(buffer__WEBPACK_IMPORTED_MODULE_7__.Buffer.from(greenPng, "base64"));_context22.next = 5;return ( + load(function () { + image.src = "image-reload-test:///green.png"; + }));case 5: - test("reports load errors and can recover with data and URL loads", function () { - var image = new _native.Image(); - var errorCount = 0; - return new Promise(function (resolve, reject) { - image.onload = function () { reject(new Error("A missing file unexpectedly loaded")); }; - image.onerror = function () { ++errorCount; resolve(); }; - image.src = "app:///Assets/nonexistent-image-reload.png"; - }).then(function () { - expect(errorCount).to.equal(1); - return [dataUrl, url].reduce(function (pending, source) { - return pending.then(function () { - return new Promise(function (resolve, reject) { - image.onload = resolve; - image.onerror = reject; - image.src = source; - }).then(function () { - expect(image.src).to.equal(source); - expect(image.width).to.equal(source === url ? 2 : 3); - }); - }); - }, Promise.resolve()); - }); - }); + context.clearRect(0, 0, 4, 4); + context.drawImage(image, 0, 0, 4, 4); + expectChannel(sample(), 1);case 6:_context22.prev = 6; + + context.dispose(); + canvas.dispose();return _context22.finish(6);case 7:case "end":return _context22.stop();}}, _callee19, null, [[1,, 6, 7]]);})) + + ); + };for (var _i21 = 0, _arr21 = [false, true]; _i21 < _arr21.length; _i21++) {_loop8();} + + test("reports load errors and can recover with data and URL loads", /*#__PURE__*/(0,_babel_runtime_helpers_asyncToGenerator__WEBPACK_IMPORTED_MODULE_1__["default"])(/*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _callee20() {var image, errorCount, _loop9, _i22, _arr22;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context24) {while (1) switch (_context24.prev = _context24.next) {case 0: + image = new _native.Image(); + errorCount = 0;_context24.next = 1;return ( + new Promise(function (resolve, reject) { + image.onload = function () {return reject(new Error("A missing file unexpectedly loaded"));}; + image.onerror = function () {++errorCount;resolve();}; + image.src = "app:///Assets/nonexistent-image-reload.png"; + }));case 1: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(errorCount).to.equal(1);_loop9 = /*#__PURE__*/_babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().mark(function _loop9() {var source;return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_2___default().wrap(function (_context23) {while (1) switch (_context23.prev = _context23.next) {case 0: + source = _arr22[_i22];_context23.next = 1;return ( + new Promise(function (resolve, reject) { + image.onload = resolve; + image.onerror = reject; + image.src = source; + }));case 1: + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.src).to.equal(source); + (0,chai__WEBPACK_IMPORTED_MODULE_4__.expect)(image.width).to.equal(source === url ? 2 : 3);case 2:case "end":return _context23.stop();}}, _loop9);});_i22 = 0, _arr22 = [dataUrl, url];case 2:if (!(_i22 < _arr22.length)) {_context24.next = 4;break;}return _context24.delegateYield(_loop9(), "t0", 3);case 3:_i22++;_context24.next = 2;break;case 4:case "end":return _context24.stop();}}, _callee20);})) + + ); }); + mocha.run(function (failures) { // Test program will wait for code to be set before exiting if (failures > 0) { From e5ec22683c9bafe20a5575baf912b95fd43eccda Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Mon, 5 Oct 2026 20:48:39 -0700 Subject: [PATCH 16/16] Enable 57 rendering validation scenes Enable the independent rendering cases supported by the reduced native stack. Defer newly enabled cases still failing stock CI, restoring their upstream catalog entries without disabling any upstream-enabled tests. Keep the original pixel budgets, references, scene IDs, render counts, and explicit backend exclusions. The catalog enables 371 of 720 cases. Babylon.js and native dependency pins remain unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 60c2ec68-6de1-445d-9fc9-b699db737eae --- Apps/Playground/Scripts/config.json | 142 +++------------------------- 1 file changed, 14 insertions(+), 128 deletions(-) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 43239db43..75c3cbf88 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -266,9 +266,7 @@ { "title": "Fresnel", "playgroundId": "#603JUZ#1", - "referenceImage": "fresnel.png", - "excludeFromAutomaticTesting": true, - "reason": "Suspected to corrupt state causing later Win32 D3D11 cascade crash; also pixel diff fail on Linux" + "referenceImage": "fresnel.png" }, { "title": "Unindexed mesh", @@ -548,8 +546,6 @@ { "title": "Merge Meshes with submeshes", "playgroundId": "#ZHPFRQ", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "mergemeshes-sub.png" }, { @@ -583,8 +579,6 @@ "title": "Gaussian Splatting Depth Alpha Blended", "playgroundId": "#V80DRL#19", "renderCount": 15, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "gsplat-depth-alpha-blended.png" }, { @@ -599,8 +593,6 @@ "title": "Gaussian Splatting viewports", "playgroundId": "#CG8GO3#1", "renderCount": 15, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (large diff)", "referenceImage": "gsplat-viewports.png" }, { @@ -625,16 +617,12 @@ "title": "SOGS with SH", "playgroundId": "#QA2662#13", "renderCount": 15, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "gsplat-sogs-sh.png" }, { "title": "Gaussian Splatting Part Test", "playgroundId": "#BTS11N#0", "renderCount": 15, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "gsplat-part-test.png" }, { @@ -863,8 +851,6 @@ { "title": "GPUPicker", "playgroundId": "#1RWFMT#8", - "excludeFromAutomaticTesting": true, - "reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up.", "referenceImage": "gpuPicker.png" }, { @@ -906,22 +892,16 @@ { "title": "GreasedLine - basic", "playgroundId": "#H1LRZ3#103", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "greased-line-basic.png" }, { "title": "GreasedLine - simple material", "playgroundId": "#H1LRZ3#427", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "greased-line-simple-material.png" }, { "title": "MeshBuilder.CreateText", "playgroundId": "#6I2RMN#2", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "createText.png" }, { @@ -981,8 +961,6 @@ { "title": "Node material 2", "playgroundId": "#WYM31D#13", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (7283 px diff)", "referenceImage": "node-material2.png" }, { @@ -993,8 +971,6 @@ { "title": "Node material 4", "playgroundId": "#ZYGRII#1", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (6347 px diff)", "referenceImage": "node-material4.png" }, { @@ -1005,15 +981,11 @@ { "title": "Node material 6", "playgroundId": "#2XY3Z4#3", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (68783 px diff)", "referenceImage": "node-material6.png" }, { "title": "Node material 7", "playgroundId": "#10I31V#323", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (77539 px diff)", "referenceImage": "node-material7.png" }, { @@ -1039,8 +1011,6 @@ { "title": "Nested BBG", "playgroundId": "#ZG0C8B#12", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "nested-BBG.png" }, { @@ -1074,9 +1044,7 @@ { "title": "GUI StackPanel", "playgroundId": "#LLVZ90#0", - "referenceImage": "StackPanel.png", - "excludeFromAutomaticTesting": true, - "reason": "GUI StackPanel buttons render orange/red instead of green (BN GUI color regression)." + "referenceImage": "StackPanel.png" }, { "title": "GUI Slate", @@ -1095,8 +1063,6 @@ { "title": "Camera rig", "playgroundId": "#ATL1CS#12", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "cameraRig.png" }, { @@ -1123,15 +1089,11 @@ "title": "Clip planes", "playgroundId": "#Y6W087#0", "renderCount": 5, - "referenceImage": "clipplanes.png", - "excludeFromAutomaticTesting": true, - "reason": "GUI slider handles render red instead of green (BN GUI color regression); skull also has minor edge anti-aliasing differences." + "referenceImage": "clipplanes.png" }, { "title": "ShadowOnlyMaterial", "playgroundId": "#1KF7V1#18", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "shadowOnlyMaterial.png" }, { @@ -1144,8 +1106,6 @@ "title": "Particle Helper", "playgroundId": "#1VGT5D#2", "renderCount": 50, - "excludeFromAutomaticTesting": true, - "reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up.", "referenceImage": "particle-helper.png" }, { @@ -1174,9 +1134,7 @@ { "title": "Fresnel", "playgroundId": "#603JUZ#3", - "referenceImage": "fresnel.png", - "excludeFromAutomaticTesting": true, - "reason": "Suspected to corrupt state causing later Win32 D3D11 cascade crash; also pixel diff fail on Linux" + "referenceImage": "fresnel.png" }, { "title": "Highlights", @@ -1290,8 +1248,6 @@ "title": "Draco Mesh Compression (fallback)", "playgroundId": "#22MFU2#75", "replace": "numWorkers = 0, numWorkers = 1", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "draco.png" }, { @@ -1526,9 +1482,7 @@ "title": "Depth of field", "playgroundId": "#IDSQK2#86", "renderCount": 20, - "referenceImage": "depthOfField.png", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (10675 px diff)" + "referenceImage": "depthOfField.png" }, { "title": "Default pipeline", @@ -1616,9 +1570,7 @@ "title": "MultiSample render targets", "playgroundId": "#12MKMN#0", "renderCount": 20, - "referenceImage": "MultiSample-render-targets.png", - "excludeFromAutomaticTesting": true, - "reason": "Disabled to land sync PR; newly-added test in cascade path of Win32 V8 D3D11 ACCESS_VIOLATION (-1073741819). Will be re-enabled in follow-up fix PR." + "referenceImage": "MultiSample-render-targets.png" }, { "title": "Default rendering pipeline", @@ -1650,9 +1602,7 @@ "title": "Local cubemaps", "playgroundId": "#RNASML#4", "renderCount": 2, - "referenceImage": "local-cubemaps.png", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails on Linux (125368 px diff)" + "referenceImage": "local-cubemaps.png" }, { "title": "TGA", @@ -1771,16 +1721,12 @@ { "title": "glTF to OBJ (LH)", "playgroundId": "#HYZWGK#3", - "excludeFromAutomaticTesting": true, - "reason": "Hangs on Win32 Chakra D3D11 - glTF to OBJ round trip", "referenceImage": "gltfToObjLH.png" }, { "title": "glTF to OBJ (RH)", "playgroundId": "#HYZWGK#3", "replace": "//options//, useRightHandedSystem = true;", - "excludeFromAutomaticTesting": true, - "reason": "Hangs on Win32 Chakra D3D11 - glTF to OBJ round trip", "referenceImage": "gltfToObjRH.png" }, { @@ -1818,16 +1764,12 @@ "title": "Shadows with instances in left handed system", "playgroundId": "#MSAHKR#79", "renderCount": 10, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "shadowsinstancesleft.png" }, { "title": "Shadows with instances in right handed system", "playgroundId": "#MSAHKR#13", "renderCount": 10, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "shadowsinstancesright.png" }, { @@ -2078,24 +2020,18 @@ "title": "Instances manual update + motion blur", "playgroundId": "#HJGC2G#127", "renderCount": 10, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "instances-mb-manual.png" }, { "title": "Thin instances + motion blur", "playgroundId": "#HJGC2G#60", "renderCount": 30, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "thin-instances-mb.png" }, { "title": "Thin instances + motion blur + manual", "playgroundId": "#HJGC2G#245", "renderCount": 30, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "thin-instances-mb-manual.png" }, { @@ -2191,16 +2127,12 @@ "title": "Bones and morphs computation order", "playgroundId": "#WGZLGJ#4040", "renderCount": 40, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "bonesMorphsCompute.png" }, { "title": "DepthRenderer rhs", "playgroundId": "#1PHYB0#138", "renderCount": 5, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "depthRendererRHS.png" }, { @@ -2224,8 +2156,6 @@ "title": "Picking Visual Test", "playgroundId": "#4J7Y7V#1", "renderCount": 2, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "pickingVisualTest.png" }, { @@ -2298,15 +2228,11 @@ { "title": "GUI Input Text Area Inside ScrollViewer", "playgroundId": "#NVAEWD#69", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "GUI-Input-Text-Area-Inside-ScrollViewer.png" }, { "title": "GUI Images in Grid", "playgroundId": "#8Y0XJL#8", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "GUI-Images-in-Grid.png" }, { @@ -2334,8 +2260,6 @@ { "title": "Load babylon files with same geometry uniqueId", "playgroundId": "#3UCFHS", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "Load-babylon-files-with-same-geometry-uniqueId.png" }, { @@ -2409,30 +2333,22 @@ { "title": "Sprites Pixel Perfect", "playgroundId": "#ZX8DJ3#10", - "excludeFromAutomaticTesting": true, - "reason": "NativeEngine Sprite renderer shows opaque black background instead of transparent pixels around sprite quad; sprite alpha-blending regression.", "referenceImage": "Sprites-Pixel-Perfect.png" }, { "title": "Rounding values on controls inside StackPanel", "playgroundId": "#XCPP9Y#18325", - "referenceImage": "Rounding-values-on-controls-inside-StackPanel.png", - "excludeFromAutomaticTesting": true, - "reason": "GUI StackPanel buttons render orange/red instead of green (BN GUI color regression)." + "referenceImage": "Rounding-values-on-controls-inside-StackPanel.png" }, { "title": "Rounding cell widths on Grid", "playgroundId": "#PQ4G5G#2", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "Rounding-cell-widths-on-Grid.png" }, { "title": "GUI for Mesh cloning", "playgroundId": "#ZUAPLL#2", - "referenceImage": "GUI-for-Mesh-cloning.png", - "excludeFromAutomaticTesting": true, - "reason": "Disabled to land sync PR; newly-added test in cascade path of Win32 V8 D3D11 ACCESS_VIOLATION (-1073741819). Will be re-enabled in follow-up fix PR." + "referenceImage": "GUI-for-Mesh-cloning.png" }, { "title": "PBR Debug Modes", @@ -2462,9 +2378,7 @@ { "title": "SerializeScene and ImportMesh with MorphTargetManager", "playgroundId": "#HPV2TZ#378", - "referenceImage": "SerializeScene-and-ImportMesh-with-MorphTargetManager.png", - "excludeFromAutomaticTesting": true, - "reason": "Pixel difference (~6,369 pixels) on Linux OpenGL after newly synced from Babylon.js." + "referenceImage": "SerializeScene-and-ImportMesh-with-MorphTargetManager.png" }, { "title": "Vertex buffers with non aligned strides", @@ -2509,9 +2423,7 @@ { "title": "Command encoder order in WebGPU 2", "playgroundId": "#CMH5VF#7", - "referenceImage": "CommandEncodedWebGPU2.png", - "excludeFromAutomaticTesting": true, - "reason": "ASSERT/crash (exit 133) on Linux OpenGL after newly synced from Babylon.js." + "referenceImage": "CommandEncodedWebGPU2.png" }, { "title": "edge-renderer-and-zOffset", @@ -2527,8 +2439,6 @@ { "title": "water-material", "playgroundId": "#EUH2VL#1", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "water-material.png" }, { @@ -2636,8 +2546,6 @@ "title": "dissolve-effect-with-node-material-and-glow-layer", "playgroundId": "#F6PGMC#12", "renderCount": 2, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "dissolve-effect-with-node-material-and-glow-layer.png" }, { @@ -2658,8 +2566,6 @@ "title": "depth-renderer", "playgroundId": "#1PHYB0#112", "renderCount": 5, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "depth-renderer.png" }, { @@ -2671,9 +2577,7 @@ { "title": "lens-flare", "playgroundId": "#KICG93#2", - "referenceImage": "lens-flare.png", - "excludeFromAutomaticTesting": true, - "reason": "Newly added test fails: massive pixel difference (240k pixels off) on Linux Clang/GCC JSC, Win32 D3D11, and Sanitizers." + "referenceImage": "lens-flare.png" }, { "title": "change-texture-of-material", @@ -2775,8 +2679,6 @@ { "title": "Glow layer with vertex alpha", "playgroundId": "#GWAX2G#7", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "Glow-layer-with-vertex-alpha.png" }, { @@ -2912,16 +2814,12 @@ { "title": "FrameGraph nrge frozen meshes", "playgroundId": "#9SDNZF#34", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "FrameGraph-nrge-frozen-meshes.png" }, { "title": "FrameGraph nrge TAA", "playgroundId": "#IG8NRC#87", "renderCount": 15, - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "FrameGraph-nrge-TAA.png" }, { @@ -3004,8 +2902,6 @@ { "title": "FrameGraph nrg gui bloom", "playgroundId": "#3LB017#3", - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "FrameGraph-nrg-gui-bloom.png" }, { @@ -4041,9 +3937,7 @@ "title": "NME Particles with PositionW", "playgroundId": "#RA18GJ#17", "renderCount": 120, - "referenceImage": "NME-Particles-with-PositionW.png", - "excludeFromAutomaticTesting": true, - "reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up." + "referenceImage": "NME-Particles-with-PositionW.png" }, { "title": "Particles - Basic Properties - Size", @@ -4332,9 +4226,7 @@ "title": "Particles - Billboard False", "playgroundId": "#XWG0HV#4", "renderCount": 120, - "referenceImage": "particles-billboard-false.png", - "excludeFromAutomaticTesting": true, - "reason": "Pixel diff after #1691 instance-data stride fix; not stride-related, needs follow-up." + "referenceImage": "particles-billboard-false.png" }, { "title": "Particles - Multiply Blend", @@ -4390,16 +4282,12 @@ "title": "Particles - Flowmaps", "playgroundId": "#M58G3X#4", "renderCount": 240, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "particles-flowmaps.png" }, { "title": "Particles - Flowmaps 2", "playgroundId": "#PPC2EI#5", "renderCount": 120, - "excludeFromAutomaticTesting": true, - "reason": "Pixel comparison fails (more than 20% pixels differ)", "referenceImage": "particles-flowmaps-2.png" }, { @@ -4701,8 +4589,6 @@ { "title": "Reverse depth buffer fix flicker", "playgroundId": "#67JFXI#15", - "excludeFromAutomaticTesting": true, - "reason": "Requires WebGL2-only feature (not supported by Babylon Native)", "referenceImage": "reverseDepthBufferFixFlicker.png" }, {