-
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
Merged
Merged
Changes from 19 commits
Commits
Show all changes
26 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 d3acc69
Debugging notes
alexchuber baa96e0
Remove const using cast in order to prevent copy
alexchuber d3a8d9c
Experiment with shared_ptr return
alexchuber dde1c28
Experiment with unique_ptr return
alexchuber 903738d
Notes
alexchuber 40b4628
PR feedback
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 | ||
alexchuber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #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 | ||
alexchuber marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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::make_shared<std::vector<uint8_t>>(buffer.Data(), buffer.Data() + buffer.ByteLength())}; | ||
|
|
||
| arcana::make_task(arcana::threadpool_scheduler, arcana::cancellation_source::none(), | ||
| [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, | ||
alexchuber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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()))}; | ||
alexchuber marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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.