- 
                Notifications
    
You must be signed in to change notification settings  - Fork 3.6k
 
DumpData: Add Native override #17365
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
base: master
Are you sure you want to change the base?
Changes from all commits
ffa3e0f
              6a82e50
              7292799
              42b80fb
              0774434
              d91fbff
              b6b3b36
              5dcb498
              436a9cf
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -9,10 +9,12 @@ import { Clamp } from "../Maths/math.scalar.functions"; | |
| import type { AbstractEngine } from "../Engines/abstractEngine"; | ||
| import { EngineStore } from "../Engines/engineStore"; | ||
| import { Logger } from "./logger"; | ||
| import { EncodeArrayBufferToBase64 } from "./stringTools"; | ||
| import { nativeOverride } from "./decorators"; | ||
| 
     | 
||
| type DumpResources = { | ||
| canvas: HTMLCanvasElement | OffscreenCanvas; | ||
| dumpEngine?: { | ||
| dumpEngine: { | ||
| engine: ThinEngine; | ||
| renderer: EffectRenderer; | ||
| wrapper: EffectWrapper; | ||
| 
        
          
        
         | 
    @@ -28,14 +30,11 @@ async function _CreateDumpResourcesAsync(): Promise<DumpResources> { | |
| Logger.Warn("DumpData: OffscreenCanvas will be used for dumping data. This may result in lossy alpha values."); | ||
| } | ||
| 
     | 
||
| // If WebGL via ThinEngine is not available (e.g. Native), use the BitmapRenderer. | ||
| // If WebGL via ThinEngine is not available, we cannot encode the data. | ||
| // If https://github.com/whatwg/html/issues/10142 is resolved, we can migrate to just BitmapRenderer and avoid an engine dependency altogether. | ||
| const { ThinEngine: thinEngineClass } = await import("../Engines/thinEngine"); | ||
| if (!thinEngineClass.IsSupported) { | ||
| if (!canvas.getContext("bitmaprenderer")) { | ||
| throw new Error("DumpData: No WebGL or bitmap rendering context available. Cannot dump data."); | ||
| } | ||
| return { canvas }; | ||
| throw new Error("DumpData: No WebGL context available. Cannot dump data."); | ||
| } | ||
| 
     | 
||
| const options = { | ||
| 
          
            
          
           | 
    @@ -85,6 +84,59 @@ async function _GetDumpResourcesAsync() { | |
| return await ResourcesPromise; | ||
| } | ||
| 
     | 
||
| class EncodingHelper { | ||
| /** | ||
| * Encodes image data to the given mime type. | ||
| * This is put into a helper class so we can apply the nativeOverride decorator to it. | ||
| * @internal | ||
| */ | ||
| @nativeOverride | ||
                
      
                  bghgary marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| public static async EncodeImageAsync(pixelData: ArrayBufferView, width: number, height: number, mimeType?: string, invertY?: boolean, quality?: number): Promise<Blob> { | ||
| const resources = await _GetDumpResourcesAsync(); | ||
| 
     | 
||
| // TODO: No need to promisify this whole thing | ||
| return await new Promise<Blob>((resolve, reject) => { | ||
| const dumpEngine = resources.dumpEngine; | ||
| dumpEngine.engine.setSize(width, height, true); | ||
| 
     | 
||
| // Create the image | ||
| const texture = dumpEngine.engine.createRawTexture(pixelData, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); | ||
| 
     | 
||
| dumpEngine.renderer.setViewport(); | ||
| dumpEngine.renderer.applyEffectWrapper(dumpEngine.wrapper); | ||
| dumpEngine.wrapper.effect._bindTexture("textureSampler", texture); | ||
| dumpEngine.renderer.draw(); | ||
| 
     | 
||
| texture.dispose(); | ||
| 
     | 
||
| Tools.ToBlob( | ||
| 
         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. Should we add a  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. We could. It wouldn't be as simple as replacing with ToBlob with ToBlobAsync, though, as the canvas draw + toBlob must be atomic. Guess we could use an AsyncLock? 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. Not sure what you mean. We can chat, or if you want to just leave it as is that's ok too. 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. Synced offline. My previous comment was probably incorrect, and we should be able to directly replace with something like ToBlobAsync. My question is-- where to put it? ATM, ToBlob exists as a static method in Tools. Maybe this can be put off for another PR.  | 
||
| resources.canvas, | ||
| (blob) => { | ||
| if (!blob) { | ||
| reject(new Error("EncodeImageAsync: Failed to convert canvas to blob.")); | ||
| } else { | ||
| resolve(blob); | ||
| } | ||
| }, | ||
| mimeType, | ||
| quality | ||
| ); | ||
| }); | ||
| } | ||
| } | ||
| 
     | 
||
| /** | ||
| * Encodes pixel data to an image | ||
| * @param pixelData 8-bit RGBA pixel data | ||
| * @param width the width of the image | ||
| * @param height the height of the image | ||
| * @param mimeType the requested MIME type | ||
| * @param invertY true to invert the image in the Y direction | ||
| * @param quality the quality of the image if lossy mimeType is used (e.g. image/jpeg, image/webp). See {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob | HTMLCanvasElement.toBlob()}'s `quality` parameter. | ||
| * @returns a promise that resolves to the encoded image data. Note that the `blob.type` may differ from `mimeType` if it was not supported. | ||
| */ | ||
| export const EncodeImageAsync = EncodingHelper.EncodeImageAsync; | ||
| 
     | 
||
| /** | ||
| * Dumps the current bound framebuffer | ||
| * @param width defines the rendering width | ||
| 
          
            
          
           | 
    @@ -168,63 +220,23 @@ export async function DumpDataAsync( | |
| data = data2; | ||
| } | ||
| 
     | 
||
| const resources = await _GetDumpResourcesAsync(); | ||
| const blob = await EncodingHelper.EncodeImageAsync(data, width, height, mimeType, invertY, quality); | ||
| 
     | 
||
| // Keep the async render + read from the shared canvas atomic | ||
| // eslint-disable-next-line no-async-promise-executor | ||
| return await new Promise<string | ArrayBuffer>(async (resolve) => { | ||
| if (resources.dumpEngine) { | ||
| const dumpEngine = resources.dumpEngine; | ||
| dumpEngine.engine.setSize(width, height, true); | ||
| 
     | 
||
| // Create the image | ||
| const texture = dumpEngine.engine.createRawTexture(data, width, height, Constants.TEXTUREFORMAT_RGBA, false, !invertY, Constants.TEXTURE_NEAREST_NEAREST); | ||
| 
     | 
||
| dumpEngine.renderer.setViewport(); | ||
| dumpEngine.renderer.applyEffectWrapper(dumpEngine.wrapper); | ||
| dumpEngine.wrapper.effect._bindTexture("textureSampler", texture); | ||
| dumpEngine.renderer.draw(); | ||
| if (fileName !== undefined) { | ||
| Tools.DownloadBlob(blob, fileName); | ||
| } | ||
| 
     | 
||
| texture.dispose(); | ||
| } else { | ||
| const ctx = resources.canvas.getContext("bitmaprenderer") as ImageBitmapRenderingContext; | ||
| resources.canvas.width = width; | ||
| resources.canvas.height = height; | ||
| if (blob.type !== mimeType) { | ||
| Logger.Warn(`DumpData: The requested mimeType '${mimeType}' is not supported. The result has mimeType '${blob.type}' instead.`); | ||
| } | ||
| 
     | 
||
| const imageData = new ImageData(width, height); // ImageData(data, sw, sh) ctor not yet widely implemented | ||
| imageData.data.set(data as Uint8ClampedArray); | ||
| const imageBitmap = await createImageBitmap(imageData, { premultiplyAlpha: "none", imageOrientation: invertY ? "flipY" : "from-image" }); | ||
| const buffer = await blob.arrayBuffer(); | ||
| 
     | 
||
| ctx.transferFromImageBitmap(imageBitmap); | ||
| } | ||
| if (toArrayBuffer) { | ||
| return buffer; | ||
| } | ||
| 
     | 
||
| Tools.ToBlob( | ||
| resources.canvas, | ||
| (blob) => { | ||
| if (!blob) { | ||
| throw new Error("DumpData: Failed to convert canvas to blob."); | ||
| } | ||
| 
     | 
||
| if (fileName !== undefined) { | ||
| Tools.DownloadBlob(blob, fileName); | ||
| } | ||
| 
     | 
||
| const fileReader = new FileReader(); | ||
| fileReader.onload = (event: any) => { | ||
| const result = event.target!.result as string | ArrayBuffer; | ||
| resolve(result); | ||
| }; | ||
| 
     | 
||
| if (toArrayBuffer) { | ||
| fileReader.readAsArrayBuffer(blob); | ||
| } else { | ||
| fileReader.readAsDataURL(blob); | ||
| } | ||
| }, | ||
| mimeType, | ||
| quality | ||
| ); | ||
| }); | ||
| return `data:${mimeType};base64,${EncodeArrayBufferToBase64(buffer)}`; | ||
| } | ||
| 
     | 
||
| /** | ||
| 
          
            
          
           | 
    ||
Uh oh!
There was an error while loading. Please reload this page.