-
Notifications
You must be signed in to change notification settings - Fork 152
Add NativeEncoding plugin #1555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
alexchuber
wants to merge
20
commits into
BabylonJS:master
Choose a base branch
from
alexchuber:add-native-encoding
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+214
−0
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
5b174c4
Add NativeEncoding plugin
alexchuber d201676
Update installations
alexchuber 5c1468f
Add readme
alexchuber f874d8e
Capitalize and add readme
alexchuber 34db646
Add error handling
alexchuber dcccbd8
Promisify EncodeImage
alexchuber e9fd332
Asynchronize EncodeImage
alexchuber 7ae9345
Add NativeEncoding to installation test
alexchuber 7ec843e
Add unit tests
alexchuber 3002bcf
Update readme
alexchuber c8e9f3c
Remove NativeEncoding from Playground (will handle in separate PR)
alexchuber be49d00
Update tests
alexchuber bf18698
Test async operations
alexchuber 7718f35
Add comments
alexchuber 507b939
Copy buffer & tie cancellation to JS env lifetime
alexchuber ef5c562
Meh, remove cancellation source until better pattern comes up
alexchuber aa84b88
Remove teardown test (known crash) and block test (not much of a point)
alexchuber 9071bf3
Remove a copy; clean up syntax
alexchuber 1cc8713
Comments
alexchuber ea0a2b1
Remove unneeded shared_ptr
alexchuber File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| set(SOURCES | ||
| "Include/Babylon/Plugins/NativeEncoding.h" | ||
| "Source/NativeEncoding.cpp") | ||
|
|
||
| add_library(NativeEncoding ${SOURCES}) | ||
| warnings_as_errors(NativeEncoding) | ||
|
|
||
| target_include_directories(NativeEncoding | ||
| PUBLIC "Include") | ||
|
|
||
| target_link_libraries(NativeEncoding | ||
| PUBLIC napi | ||
| PRIVATE GraphicsDevice | ||
| PRIVATE GraphicsDeviceContext | ||
| PRIVATE JsRuntimeInternal) | ||
|
|
||
| set_property(TARGET NativeEncoding PROPERTY FOLDER Plugins) | ||
| source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) |
9 changes: 9 additions & 0 deletions
9
Plugins/NativeEncoding/Include/Babylon/Plugins/NativeEncoding.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| #pragma once | ||
|
|
||
| #include <napi/env.h> | ||
| #include <Babylon/Api.h> | ||
|
|
||
| namespace Babylon::Plugins::NativeEncoding | ||
| { | ||
| void BABYLON_API Initialize(Napi::Env env); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # NativeEncoding | ||
|
|
||
| > ⚠️ **This plugin is experimental and subject to change.** | ||
|
|
||
| The NativeEncoding plugin provides native image encoding capabilities to Babylon, allowing raw pixel data to be encoded into standard image formats (PNG, JPEG, WebP, etc.). | ||
|
|
||
| ## Design | ||
|
|
||
| Unlike a traditional polyfill which would implement Canvas's `toBlob()` or `toDataURL()` methods, NativeEncoding exists as a plugin because: | ||
| 1. **No standard Web API exists** for general-purpose image encoding separate from Canvas | ||
| 2. **Simplicity** - Exposes only what Babylon actually needs: direct pixel-to-bytes encoding | ||
| 3. **Efficiency** - Avoids extra routing through the Canvas API via intermediate data structures | ||
| 4. **Modularity** - Image encoding is a separate concern from 2D canvas rendering | ||
| 5. **Extensibility** - New codecs can be added in the future without bloating other components | ||
|
|
||
| An encoding function is exposed on the `_native` global object, similar to NativeOptimizations. | ||
|
|
||
| ```typescript | ||
| interface INative { | ||
| EncodeImageAsync: (pixelData: Uint8Array, width: number, height: number, mimeType: string, invertY: boolean) => Promise<ArrayBuffer>; | ||
| } | ||
| ``` | ||
|
|
||
| It should be wrapped by higher-level Babylon.js APIs (e.g., DumpTools) for common workflows like asset exports and screenshots. | ||
|
|
||
| ## Limitations | ||
|
|
||
| Currently, **only PNG encoding** is supported. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| #include <Babylon/Plugins/NativeEncoding.h> | ||
| #include <Babylon/JsRuntime.h> | ||
| #include <Babylon/JsRuntimeScheduler.h> | ||
| #include <Babylon/Graphics/DeviceContext.h> | ||
|
|
||
| #include <napi/napi.h> | ||
|
|
||
| #include <bimg/encode.h> | ||
| #include <bx/readerwriter.h> | ||
|
|
||
| #include <arcana/threading/task.h> | ||
| #include <arcana/threading/task_schedulers.h> | ||
|
|
||
| namespace Babylon::Plugins | ||
| { | ||
| namespace | ||
| { | ||
| std::vector<uint8_t> EncodePNG(const std::vector<uint8_t>& pixelData, uint32_t width, uint32_t height, bool invertY) | ||
| { | ||
| auto memoryBlock{bx::MemoryBlock(&Graphics::DeviceContext::GetDefaultAllocator())}; | ||
| auto writer{bx::MemoryWriter(&memoryBlock)}; | ||
| auto err{bx::Error()}; | ||
|
|
||
| bimg::imageWritePng(&writer, width, height, width * 4, pixelData.data(), bimg::TextureFormat::RGBA8, !invertY, &err); | ||
|
|
||
| auto byteLength{memoryBlock.getSize()}; | ||
|
|
||
| if (!err.isOk()) | ||
| { | ||
| throw std::runtime_error("Failed to encode PNG image: " + std::string(err.getMessage().getCPtr())); | ||
| } | ||
|
|
||
| if (byteLength == 0) | ||
| { | ||
| throw std::runtime_error("Failed to encode PNG image: output is empty"); | ||
| } | ||
|
|
||
| auto result{std::vector<uint8_t>(byteLength)}; | ||
| std::memcpy(result.data(), memoryBlock.more(0), byteLength); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| Napi::Promise EncodeImageAsync(const Napi::CallbackInfo& info) | ||
| { | ||
| auto buffer{info[0].As<Napi::Uint8Array>()}; | ||
| auto width{info[1].As<Napi::Number>().Uint32Value()}; | ||
| auto height{info[2].As<Napi::Number>().Uint32Value()}; | ||
| auto mimeType{info[3].As<Napi::String>().Utf8Value()}; | ||
| auto invertY{info[4].As<Napi::Boolean>().Value()}; | ||
|
|
||
| auto env{info.Env()}; | ||
| auto deferred{Napi::Promise::Deferred::New(env)}; | ||
|
|
||
| if (mimeType != "image/png") | ||
| { | ||
| deferred.Reject(Napi::Error::New(env, "Unsupported mime type: " + mimeType + ". Only image/png is currently supported.").Value()); | ||
| return deferred.Promise(); | ||
| } | ||
|
|
||
| if (buffer.ByteLength() != width * height * 4) | ||
| { | ||
| deferred.Reject(Napi::Error::New(env, "Buffer byte length does not match RGBA8 format (4 bytes per pixel) of provided dimensions.").Value()); | ||
| return deferred.Promise(); | ||
| } | ||
|
|
||
| auto runtimeScheduler{std::make_shared<JsRuntimeScheduler>(JsRuntime::GetFromJavaScript(env))}; | ||
| auto pixelData{std::vector<uint8_t>(buffer.Data(), buffer.Data() + buffer.ByteLength())}; | ||
|
|
||
| arcana::make_task(arcana::threadpool_scheduler, arcana::cancellation_source::none(), | ||
| [pixelData{std::move(pixelData)}, width, height, invertY]() { | ||
| return EncodePNG(pixelData, width, height, invertY); | ||
| }) | ||
| .then(*runtimeScheduler, arcana::cancellation_source::none(), | ||
| [runtimeScheduler, deferred, env](const arcana::expected<std::vector<uint8_t>, std::exception_ptr>& result) { | ||
| // TODO: Crash risk on JS teardown - this async work isn't tied to any JS object lifetime, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See issue #1558 |
||
| // unlike other plugins that cancel / clean up pending work in their destructors. | ||
| if (result.has_error()) | ||
| { | ||
| deferred.Reject(Napi::Error::New(env, result.error()).Value()); | ||
| return; | ||
| } | ||
|
|
||
| auto imageData{std::make_shared<std::vector<uint8_t>>(std::move(result.value()))}; | ||
| auto arrayBuffer{Napi::ArrayBuffer::New(env, imageData->data(), imageData->size(), [imageData](Napi::Env, void*){})}; | ||
|
|
||
| deferred.Resolve(arrayBuffer); | ||
| }); | ||
|
|
||
| return deferred.Promise(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| namespace Babylon::Plugins::NativeEncoding | ||
| { | ||
| void BABYLON_API Initialize(Napi::Env env) | ||
| { | ||
| auto native{JsRuntime::NativeObject::GetFromJavaScript(env)}; | ||
| native.Set("EncodeImageAsync", Napi::Function::New(env, EncodeImageAsync, "EncodeImageAsync")); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.