From b898366a96f148fa31b74e3cc9ec9928b2ad2630 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sat, 4 Mar 2023 14:31:27 +0100 Subject: [PATCH 01/15] started webgpu driver --- h3d/Engine.hx | 40 +- h3d/impl/WebGpuApi.hx | 1920 ++++++++++++++++++++++++++++++++++++++ h3d/impl/WebGpuDriver.hx | 176 ++++ hxd/System.js.hx | 7 + 4 files changed, 2124 insertions(+), 19 deletions(-) create mode 100644 h3d/impl/WebGpuApi.hx create mode 100644 h3d/impl/WebGpuDriver.hx diff --git a/h3d/Engine.hx b/h3d/Engine.hx index e349f75c77..b9474dc62a 100644 --- a/h3d/Engine.hx +++ b/h3d/Engine.hx @@ -68,30 +68,32 @@ class Engine { lastTime = haxe.Timer.stamp(); window.addResizeEvent(onWindowResize); #if macro - driver = new h3d.impl.NullDriver(); - #elseif (js || hlsdl || usegl) - #if (hlsdl && heaps_vulkan) - if( hxd.Window.USE_VULKAN ) - driver = new h3d.impl.VulkanDriver(); - else - #end - #if js - driver = js.Browser.supported ? new h3d.impl.GlDriver(antiAlias) : new h3d.impl.NullDriver(); - #else - driver = new h3d.impl.GlDriver(antiAlias); - #end + driver = new h3d.impl.NullDriver(); + #elseif js + #if webgpu + driver = new h3d.impl.WebGpuDriver(); + #else + driver = js.Browser.supported ? new h3d.impl.GlDriver(antiAlias) : new h3d.impl.NullDriver(); + #end + #elseif (hlsdl || usegl) + #if heaps_vulkan + if( hxd.Window.USE_VULKAN ) + driver = new h3d.impl.VulkanDriver(); + else + #end + driver = new h3d.impl.GlDriver(antiAlias); #elseif flash - driver = new h3d.impl.Stage3dDriver(antiAlias); + driver = new h3d.impl.Stage3dDriver(antiAlias); #elseif (hldx && dx12) - driver = new h3d.impl.DX12Driver(); + driver = new h3d.impl.DX12Driver(); #elseif hldx - driver = new h3d.impl.DirectXDriver(); + driver = new h3d.impl.DirectXDriver(); #elseif usesys - driver = new haxe.GraphicsDriver(antiAlias); + driver = new haxe.GraphicsDriver(antiAlias); #else - #if sys Sys.println #else trace #end("No output driver available." #if hl + " Compile with -lib hlsdl or -lib hldx" #end); - driver = new h3d.impl.LogDriver(new h3d.impl.NullDriver()); - driver.logEnable = true; + #if sys Sys.println #else trace #end("No output driver available." #if hl + " Compile with -lib hlsdl or -lib hldx" #end); + driver = new h3d.impl.LogDriver(new h3d.impl.NullDriver()); + driver.logEnable = true; #end setCurrent(); } diff --git a/h3d/impl/WebGpuApi.hx b/h3d/impl/WebGpuApi.hx new file mode 100644 index 0000000000..55a271e635 --- /dev/null +++ b/h3d/impl/WebGpuApi.hx @@ -0,0 +1,1920 @@ +/** + WebGPU API. + Mostly extracted from https://github.com/gpuweb/types/blob/main/generated/index.d.ts + And https://www.w3.org/TR/webgpu/ +**/ +package h3d.impl; + +import js.lib.Promise; +import js.lib.BufferSource; +import haxe.ds.ReadOnlyArray; + +typedef GPUSize64 = Int; +typedef GPUIntegerCoordinate = Int; +typedef GPUSize32 = Int; +typedef GPUIndex32 = Int; +typedef GPUSignedOffset32 = Int; + +enum abstract GPUPowerPreference(String) { + var LowPower = "low-power"; + var HighPerformance = "high-performance"; +} + +enum abstract GPUTextureFormat(String) { + var R8unorm = "r8unorm"; + var R8snorm = "r8snorm"; + var R8uint = "r8uint"; + var R8sint = "r8sint"; + var R16uint = "r16uint"; + var R16sint = "r16sint"; + var R16float = "r16float"; + var Rg8unorm = "rg8unorm"; + var Rg8snorm = "rg8snorm"; + var Rg8uint = "rg8uint"; + var Rg8sint = "rg8sint"; + var R32uint = "r32uint"; + var R32sint = "r32sint"; + var R32float = "r32float"; + var Rg16uint = "rg16uint"; + var Rg16sint = "rg16sint"; + var Rg16float = "rg16float"; + var Rgba8unorm = "rgba8unorm"; + var Rgba8unorm_srgb = "rgba8unorm-srgb"; + var Rgba8snorm = "rgba8snorm"; + var Rgba8uint = "rgba8uint"; + var Rgba8sint = "rgba8sint"; + var Bgra8unorm = "bgra8unorm"; + var Bgra8unorm_srgb = "bgra8unorm-srgb"; + var Rgb9e5ufloat = "rgb9e5ufloat"; + var Rgb10a2unorm = "rgb10a2unorm"; + var Rg11b10ufloat = "rg11b10ufloat"; + var Rg32uint = "rg32uint"; + var Rg32sint = "rg32sint"; + var Rg32float = "rg32float"; + var Rgba16uint = "rgba16uint"; + var Rgba16sint = "rgba16sint"; + var Rgba16float = "rgba16float"; + var Rgba32uint = "rgba32uint"; + var Rgba32sint = "rgba32sint"; + var Rgba32float = "rgba32float"; + var Stencil8 = "stencil8"; + var Depth16unorm = "depth16unorm"; + var Depth24plus = "depth24plus"; + var Depth24plus_stencil8 = "depth24plus-stencil8"; + var Depth32float = "depth32float"; + var Depth32float_stencil8 = "depth32float-stencil8"; + var Bc1_rgba_unorm = "bc1-rgba-unorm"; + var Bc1_rgba_unorm_srgb = "bc1-rgba-unorm-srgb"; + var Bc2_rgba_unorm = "bc2-rgba-unorm"; + var Bc2_rgba_unorm_srgb = "bc2-rgba-unorm-srgb"; + var Bc3_rgba_unorm = "bc3-rgba-unorm"; + var Bc3_rgba_unorm_srgb = "bc3-rgba-unorm-srgb"; + var Bc4_r_unorm = "bc4-r-unorm"; + var Bc4_r_snorm = "bc4-r-snorm"; + var Bc5_rg_unorm = "bc5-rg-unorm"; + var Bc5_rg_snorm = "bc5-rg-snorm"; + var Bc6h_rgb_ufloat = "bc6h-rgb-ufloat"; + var Bc6h_rgb_float = "bc6h-rgb-float"; + var Bc7_rgba_unorm = "bc7-rgba-unorm"; + var Bc7_rgba_unorm_srgb = "bc7-rgba-unorm-srgb"; + var Etc2_rgb8unorm = "etc2-rgb8unorm"; + var Etc2_rgb8unorm_srgb = "etc2-rgb8unorm-srgb"; + var Etc2_rgb8a1unorm = "etc2-rgb8a1unorm"; + var Etc2_rgb8a1unorm_srgb = "etc2-rgb8a1unorm-srgb"; + var Etc2_rgba8unorm = "etc2-rgba8unorm"; + var Etc2_rgba8unorm_srgb = "etc2-rgba8unorm-srgb"; + var Eac_r11unorm = "eac-r11unorm"; + var Eac_r11snorm = "eac-r11snorm"; + var Eac_rg11unorm = "eac-rg11unorm"; + var Eac_rg11snorm = "eac-rg11snorm"; + var Astc_4x4_unorm = "astc-4x4-unorm"; + var Astc_4x4_unorm_srgb = "astc-4x4-unorm-srgb"; + var Astc_5x4_unorm = "astc-5x4-unorm"; + var Astc_5x4_unorm_srgb = "astc-5x4-unorm-srgb"; + var Astc_5x5_unorm = "astc-5x5-unorm"; + var Astc_5x5_unorm_srgb = "astc-5x5-unorm-srgb"; + var Astc_6x5_unorm = "astc-6x5-unorm"; + var Astc_6x5_unorm_srgb = "astc-6x5-unorm-srgb"; + var Astc_6x6_unorm = "astc-6x6-unorm"; + var Astc_6x6_unorm_srgb = "astc-6x6-unorm-srgb"; + var Astc_8x5_unorm = "astc-8x5-unorm"; + var Astc_8x5_unorm_srgb = "astc-8x5-unorm-srgb"; + var Astc_8x6_unorm = "astc-8x6-unorm"; + var Astc_8x6_unorm_srgb = "astc-8x6-unorm-srgb"; + var Astc_8x8_unorm = "astc-8x8-unorm"; + var Astc_8x8_unorm_srgb = "astc-8x8-unorm-srgb"; + var Astc_10x5_unorm = "astc-10x5-unorm"; + var Astc_10x5_unorm_srgb = "astc-10x5-unorm-srgb"; + var Astc_10x6_unorm = "astc-10x6-unorm"; + var Astc_10x6_unorm_srgb = "astc-10x6-unorm-srgb"; + var Astc_10x8_unorm = "astc-10x8-unorm"; + var Astc_10x8_unorm_srgb = "astc-10x8-unorm-srgb"; + var Astc_10x10_unorm = "astc-10x10-unorm"; + var Astc_10x10_unorm_srgb = "astc-10x10-unorm-srgb"; + var Astc_12x10_unorm = "astc-12x10-unorm"; + var Astc_12x10_unorm_srgb = "astc-12x10-unorm-srgb"; + var Astc_12x12_unorm = "astc-12x12-unorm"; + var Astc_12x12_unorm_srgb = "astc-12x12-unorm-srgb"; +} + +typedef GPURequestAdapterOptions = { + var ?powerPreference : GPUPowerPreference; + var ?forceFallbackAdapter : Bool; +} + +extern class GPU { + function requestAdapter( ?options: GPURequestAdapterOptions ): Promise>; + /** + * Returns an optimal {@link GPUTextureFormat} for displaying 8-bit depth, standard dynamic range + * content on this system. Must only return {@link GPUTextureFormat#"rgba8unorm"} or + * {@link GPUTextureFormat#"bgra8unorm"}. + * The returned value can be passed as the {@link GPUCanvasConfiguration#format} to + * {@link GPUCanvasContext#configure} calls on a {@link GPUCanvasContext} to ensure the associated + * canvas is able to display its contents efficiently. + * Note: Canvases which are not displayed to the screen may or may not benefit from using this + * format. + */ + function getPreferredCanvasFormat(): GPUTextureFormat; + + /** haxe specific way to access navigator.gpu **/ + inline static function get() : GPU { + return untyped navigator.gpu; + } +} + +extern class GPUSupportedLimits { + var maxTextureDimension1D(default,null) : Float; + var maxTextureDimension2D(default,null) : Float; + var maxTextureDimension3D(default,null) : Float; + var maxTextureArrayLayers(default,null) : Float; + var maxBindGroups(default,null) : Float; + var maxBindingsPerBindGroup(default,null) : Float; + var maxDynamicUniformBuffersPerPipelineLayout(default,null) : Float; + var maxDynamicStorageBuffersPerPipelineLayout(default,null) : Float; + var maxSampledTexturesPerShaderStage(default,null) : Float; + var maxSamplersPerShaderStage(default,null) : Float; + var maxStorageBuffersPerShaderStage(default,null) : Float; + var maxStorageTexturesPerShaderStage(default,null) : Float; + var maxUniformBuffersPerShaderStage(default,null) : Float; + var maxUniformBufferBindingSize(default,null) : Float; + var maxStorageBufferBindingSize(default,null) : Float; + var minUniformBufferOffsetAlignment(default,null) : Float; + var minStorageBufferOffsetAlignment(default,null) : Float; + var maxVertexBuffers(default,null) : Float; + var maxBufferSize(default,null) : Float; + var maxVertexAttributes(default,null) : Float; + var maxVertexBufferArrayStride(default,null) : Float; + var maxInterStageShaderComponents(default,null) : Float; + var maxInterStageShaderVariables(default,null) : Float; + var maxColorAttachments(default,null) : Float; + var maxColorAttachmentBytesPerSample(default,null) : Float; + var maxComputeWorkgroupStorageSize(default,null) : Float; + var maxComputeInvocationsPerWorkgroup(default,null) : Float; + var maxComputeWorkgroupSizeX(default,null) : Float; + var maxComputeWorkgroupSizeY(default,null) : Float; + var maxComputeWorkgroupSizeZ(default,null) : Float; + var maxComputeWorkgroupsPerDimension(default,null) : Float; +} + +extern class GPUAdapterInfo { + /** + * The name of the vendor of the adapter, if available. Empty string otherwise. + */ + var vendor(default,null) : String; + /** + * The name of the family or class of GPUs the adapter belongs to, if available. Empty + * string otherwise. + */ + var architecture(default,null) : String; + /** + * A vendor-specific identifier for the adapter, if available. Empty string otherwise. + * Note: This is a value that represents the type of adapter. For example, it may be a + * [PCI device ID](https://pcisig.com/). It does not uniquely identify a given piece of + * hardware like a serial Float. + */ + var device(default,null) : String; + /** + * A human readable string describing the adapter as reported by the driver, if available. + * Empty string otherwise. + * Note: Because no formatting is applied to {@link GPUAdapterInfo#description} attempting to parse + * this value is not recommended. Applications which change their behavior based on the + * {@link GPUAdapterInfo}, such as applying workarounds for known driver issues, should rely on the + * other fields when possible. + */ + var description(default,null) : String; +} + +enum abstract GPUFeatureName(String) { + var Depth_clip_control = "depth-clip-control"; + var Depth32float_stencil8 = "depth32float-stencil8"; + var Texture_compression_bc = "texture-compression-bc"; + var Texture_compression_etc2 = "texture-compression-etc2"; + var Texture_compression_astc = "texture-compression-astc"; + var Timestamp_query = "timestamp-query"; + var Indirect_first_instance = "indirect-first-instance"; + var Shader_f16 = "shader-f16"; + var Rg11b10ufloat_renderable = "rg11b10ufloat-renderable"; + var Bgra8unorm_storage = "bgra8unorm-storage"; +} + +typedef GPUObjectDescriptorBase = { + var ?label : String; +} + +typedef GPUQueueDescriptor = GPUObjectDescriptorBase; + +typedef GPUDeviceDescriptor = {> GPUObjectDescriptorBase, + /** + * Specifies the features that are required by the device request. + * The request will fail if the adapter cannot provide these features. + * Exactly the specified set of features, and no more or less, will be allowed in validation + * of API calls on the resulting device. + */ + var ?requiredFeatures: Array; + /** + * Specifies the limits that are required by the device request. + * The request will fail if the adapter cannot provide these limits. + * Each key must be the name of a member of supported limits. + * Exactly the specified limits, and no limit/better or worse, + * will be allowed in validation of API calls on the resulting device. + * + */ + var ?requiredLimits: Dynamic; + var ?defaultQueue: GPUQueueDescriptor; +} + +typedef GPUSupportedFeatures = ReadOnlyArray; + +extern class GPUAdapter { + public var features(default,null) : GPUSupportedFeatures; + public var limits(default,null) : GPUSupportedLimits; + public var isFallbackAdapter(default,null) : Bool; + + /** + * Requests a device from the adapter. + * @param descriptor - Description of the {@link GPUDevice} to request. + */ + public function requestDevice( ?descriptor: GPUDeviceDescriptor ): Promise; + + /** + * Requests the {@link GPUAdapterInfo} for this {@link GPUAdapter}. + * Note: Adapter info values are returned with a Promise to give user agents an + * opportunity to perform potentially long-running checks when requesting unmasked values, + * such as asking for user consent before returning. If no `unmaskHints` are specified, + * however, no dialogs should be displayed to the user. + * @param unmaskHints - A list of {@link GPUAdapterInfo} attribute names for which unmasked + * values are desired if available. + */ + public function requestAdapterInfo( ?unmaskHints: Array ): Promise; + +} + +extern class GPUCommandBuffer {} + +enum abstract GPUBufferMapState(String) { + var Unmapped = "unmapped"; + var Pending = "pending"; + var Mapped = "mapped"; +} + +enum GPUMapMode { + READ; + WRITE; +} + +typedef GPUMapModeFlags = haxe.EnumFlags; + +extern class GPUBuffer { + var size(default,null) : GPUSize64; + var usage(default,null) : GPUBufferUsageFlags; + var mapState(default,null) : GPUBufferMapState; + /** + * Maps the given range of the {@link GPUBuffer} and resolves the returned {@link Promise} when the + * {@link GPUBuffer}'s content is ready to be accessed with {@link GPUBuffer#getMappedRange}. + * The resolution of the returned {@link Promise} **only** indicates that the buffer has been mapped. + * It does not guarantee the completion of any other operations visible to the content timeline, + * and in particular does not imply that any other {@link Promise} returned from + * {@link GPUQueue#onSubmittedWorkDone()} or {@link GPUBuffer#mapAsync} on other {@link GPUBuffer}s + * have resolved. + * The resolution of the {@link Promise} returned from {@link GPUQueue#onSubmittedWorkDone} + * **does** imply the completion of + * {@link GPUBuffer#mapAsync} calls made prior to that call, + * on {@link GPUBuffer}s last used exclusively on that queue. + * @param mode - Whether the buffer should be mapped for reading or writing. + * @param offset - Offset in bytes into the buffer to the start of the range to map. + * @param size - Size in bytes of the range to map. + */ + function mapAsync( mode: GPUMapModeFlags, ?offset: GPUSize64, ?size: GPUSize64 ) : Promise; + /** + * Returns an {@link ArrayBuffer} with the contents of the {@link GPUBuffer} in the given mapped range. + * @param offset - Offset in bytes into the buffer to return buffer contents from. + * @param size - Size in bytes of the {@link ArrayBuffer} to return. + */ + function getMappedRange( ?offset: GPUSize64, ?size: GPUSize64 ) : js.lib.ArrayBuffer; + /** + * Unmaps the mapped range of the {@link GPUBuffer} and makes it's contents available for use by the + * GPU again. + */ + function unmap() : Void; + /** + * Destroys the {@link GPUBuffer}. + * Note: It is valid to destroy a buffer multiple times. + * Note: Since no further operations can be enqueued using this buffer, implementations can + * free resource allocations, including mapped memory that was just unmapped. + */ + function destroy() : Void; +} + +enum abstract GPUTextureDimension(String) { + var D1 = "1d"; + var D2 = "2d"; + var D3 = "3d"; +} + +enum GPUTextureUsage { + COPY_SRC; + COPY_DST; + TEXTURE_BINDING; + STORAGE_BINDING; + RENDER_ATTACHMENT; +} + +typedef GPUTextureUsageFlags = haxe.EnumFlags; + + +enum abstract GPUTextureViewDimension(String) { + var D1 = "1d"; + var D2 = "2d"; + var D2_array = "2d-array"; + var Cube = "cube"; + var Cube_array = "cube-array"; + var D3 = "3d"; +} + +typedef GPUTextureViewDescriptor = {> GPUObjectDescriptorBase, + /** + * The format of the texture view. Must be either the {@link GPUTextureDescriptor#format} of the + * texture or one of the {@link GPUTextureDescriptor#viewFormats} specified during its creation. + */ + var ?format: GPUTextureFormat; + /** + * The dimension to view the texture as. + */ + var ?dimension: GPUTextureViewDimension; + /** + * Which {@link GPUTextureAspect|aspect(s)} of the texture are accessible to the texture view. + */ + var ?aspect: GPUTextureAspect; + /** + * The first (most detailed) mipmap level accessible to the texture view. + */ + var ?baseMipLevel: GPUIntegerCoordinate; + /** + * How many mipmap levels, starting with {@link GPUTextureViewDescriptor#baseMipLevel}, are accessible to + * the texture view. + */ + var ?mipLevelCount: GPUIntegerCoordinate; + /** + * The index of the first array layer accessible to the texture view. + */ + var ?baseArrayLayer: GPUIntegerCoordinate; + /** + * How many array layers, starting with {@link GPUTextureViewDescriptor#baseArrayLayer}, are accessible + * to the texture view. + */ + var ?arrayLayerCount: GPUIntegerCoordinate; +} + +extern class GPUTextureView { +} + +extern class GPUTexture { + /** + * Creates a {@link GPUTextureView}. + * @param descriptor - Description of the {@link GPUTextureView} to create. + */ + function createView( ?descriptor: GPUTextureViewDescriptor ) : GPUTextureView; + function destroy() : Void; + var width(default,null) : GPUIntegerCoordinate; + var height(default,null) : GPUIntegerCoordinate; + var depthOrArrayLayers(default,null) : GPUIntegerCoordinate; + var mipLevelCount(default,null) : GPUIntegerCoordinate; + var sampleCount(default,null) : GPUSize32; + var dimension(default,null) : GPUTextureDimension; + var format(default,null) : GPUTextureFormat; + var usage(default,null) : GPUTextureUsageFlags; +} + +typedef GPUOrigin2D = { + var ?x : Int; + var ?y : Int; +} + +typedef GPUOrigin3D = { + var ?x : Int; + var ?y : Int; + var ?z : Int; +} + +enum abstract GPUTextureAspect(String) { + var All = "all"; + var Stencil_only = "stencil-only"; + var Depth_only = "depth-only"; +} + +typedef GPUImageCopyTexture = { + var texture : GPUTexture; + var ?mipLevel : Int; + var ?origin : GPUOrigin3D; + var ?aspect : GPUTextureAspect; +} + +typedef GPUImageDataLayout = { + var bytesPerRow : Int; + var ?offset : Int; + var ?rowsPerImage : Int; +} + +typedef GPUExtent3D = { + var width: GPUIntegerCoordinate; + var ?height: GPUIntegerCoordinate; + var ?depthOrArrayLayers: GPUIntegerCoordinate; +} + +typedef GPUImageCopyExternalImage = { + var source : Dynamic; + var ?origin : GPUOrigin2D; + var ?flipY : Bool; +} + +// html spec +enum abstract PredefinedColorSpace(String) { + var srgb; + var Display_p3 = "display-p3"; +} + +typedef GPUImageCopyTextureTagged = { > GPUImageCopyTexture, + var ?colorSpace : PredefinedColorSpace; + var ?premultipliedAlpha : Bool; +} + +extern class GPUQueue { + /** + * Schedules the execution of the command buffers by the GPU on this queue. + * Submitted command buffers cannot be used again. + * `commandBuffers`: + */ + function submit( commandBuffers: Array ) : Void; + function onSubmittedWorkDone() : Promise; + /** + * Issues a write operation of the provided data into a {@link GPUBuffer}. + * @param buffer - The buffer to write to. + * @param bufferOffset - Offset in bytes into `buffer` to begin writing at. + * @param data - Data to write into `buffer`. + * @param dataOffset - Offset in into `data` to begin writing from. Given in elements if + * `data` is a `TypedArray` and bytes otherwise. + * @param size - Size of content to write from `data` to `buffer`. Given in elements if + * `data` is a `TypedArray` and bytes otherwise. + */ + function writeBuffer( buffer: GPUBuffer, bufferOffset: GPUSize64, data: BufferSource, ?dataOffset: GPUSize64, ?size: GPUSize64 ) : Void; + /** + * Issues a write operation of the provided data into a {@link GPUTexture}. + * @param destination - The texture subresource and origin to write to. + * @param data - Data to write into `destination`. + * @param dataLayout - Layout of the content in `data`. + * @param size - Extents of the content to write from `data` to `destination`. + */ + function writeTexture( destination: GPUImageCopyTexture, data: BufferSource, dataLayout: GPUImageDataLayout, size: GPUExtent3D ) : Void; + /** + * Issues a copy operation of the contents of a platform image/canvas + * into the destination texture. + * This operation performs [[#color-space-conversions|color encoding]] into the destination + * encoding according to the parameters of {@link GPUImageCopyTextureTagged}. + * Copying into a `-srgb` texture results in the same texture bytes, not the same decoded + * values, as copying into the corresponding non-`-srgb` format. + * Thus, after a copy operation, sampling the destination texture has + * different results depending on whether its format is `-srgb`, all else unchanged. + * + * @param source - source image and origin to copy to `destination`. + * @param destination - The texture subresource and origin to write to, and its encoding metadata. + * @param copySize - Extents of the content to write from `source` to `destination`. + */ + function copyExternalImageToTexture( source: GPUImageCopyExternalImage, destination: GPUImageCopyTextureTagged, copySize: GPUExtent3D ) : Void; +} + +enum abstract GPUDeviceLostReason(String) { + var destroyed; +} + +enum abstract GPUErrorFilter(String) { + var Validation = "validation"; + var Out_of_memory = "out-of-memory"; + var Internal = "internal"; +} + +typedef GPUDeviceLostInfo = { + final reason : GPUDeviceLostReason; + final message : String; +} + +typedef GPUError = { + /** + * A human-readable, localizable text message providing information about the error that + * occurred. + * Note: This message is generally intended for application developers to debug their + * applications and capture information for debug reports, not to be surfaced to end-users. + * Note: User agents should not include potentially machine-parsable details in this message, + * such as free system memory on {@link GPUErrorFilter#"out-of-memory"} or other details about the + * conditions under which memory was exhausted. + * Note: The {@link GPUError#message} should follow the best practices for language and + * direction information. This includes making use of any future standards which may emerge + * regarding the reporting of String language and direction metadata. + *

Editorial: + * At the time of this writing, no language/direction recommendation is available that provides + * compatibility and consistency with legacy APIs, but when there is, adopt it formally. + */ + var message(default,null) : String; +} + + +typedef GPUTextureDescriptor = {> GPUObjectDescriptorBase, + /** + * The width, height, and depth or layer count of the texture. + */ + var size : GPUExtent3D; + /** + * The Float of mip levels the texture will contain. + */ + var ?mipLevelCount: GPUIntegerCoordinate; + /** + * The sample count of the texture. A {@link GPUTextureDescriptor#sampleCount} > `1` indicates + * a multisampled texture. + */ + var ?sampleCount: GPUSize32; + /** + * Whether the texture is one-dimensional, an array of two-dimensional layers, or three-dimensional. + */ + var ?dimension: GPUTextureDimension; + /** + * The format of the texture. + */ + var format: GPUTextureFormat; + /** + * The allowed usages for the texture. + */ + var usage: GPUTextureUsageFlags; + /** + * Specifies what view {@link GPUTextureViewDescriptor#format} values will be allowed when calling + * {@link GPUTexture#createView} on this texture (in addition to the texture's actual + * {@link GPUTextureDescriptor#format}). + *

+ * Note: + * Adding a format to this list may have a significant performance impact, so it is best + * to avoid adding formats unnecessarily. + * The actual performance impact is highly dependent on the target system; developers must + * test various systems to find out the impact on their particular application. + * For example, on some systems any texture with a {@link GPUTextureDescriptor#format} or + * {@link GPUTextureDescriptor#viewFormats} entry including + * {@link GPUTextureFormat#"rgba8unorm-srgb"} will perform less optimally than a + * {@link GPUTextureFormat#"rgba8unorm"} texture which does not. + * Similar caveats exist for other formats and pairs of formats on other systems. + *
+ * Formats in this list must be texture view format compatible with the texture format. + *
+ * Two {@link GPUTextureFormat}s `format` and `viewFormat` are texture view format compatible if: + * - `format` equals `viewFormat`, or + * - `format` and `viewFormat` differ only in whether they are `srgb` formats (have the `-srgb` suffix). + * Issue(gpuweb/gpuweb#168) : Define larger compatibility classes. + *
+ */ + var ?viewFormats: Array; +} + +enum GPUBufferUsage { + MAP_READ; + MAP_WRITE; + COPY_SRC; + COPY_DST; + INDEX; + VERTEX; + UNIFORM; + STORAGE; + INDIRECT; + QUERY_RESOLVE; +} + +typedef GPUBufferUsageFlags = haxe.EnumFlags; + +typedef GPUBufferDescriptor = {> GPUObjectDescriptorBase, + /** + * The size of the buffer in bytes. + */ + var size: GPUSize64; + /** + * The allowed usages for the buffer. + */ + var usage: GPUBufferUsageFlags; + /** + * If `true` creates the buffer in an already mapped state, allowing + * {@link GPUBuffer#getMappedRange} to be called immediately. It is valid to set + * {@link GPUBufferDescriptor#mappedAtCreation} to `true` even if {@link GPUBufferDescriptor#usage} + * does not contain {@link GPUBufferUsage#MAP_READ} or {@link GPUBufferUsage#MAP_WRITE}. This can be + * used to set the buffer's initial data. + * Guarantees that even if the buffer creation eventually fails, it will still appear as if the + * mapped range can be written/read to until it is unmapped. + */ + var ?mappedAtCreation: Bool; +} + +extern class GPUShaderModule { + /** + * Returns any messages generated during the {@link GPUShaderModule}'s compilation. + * The locations, order, and contents of messages are implementation-defined. + * In particular, messages may not be ordered by {@link GPUCompilationMessage#lineNum}. + */ + function compilationInfo() : Promise; +} + +typedef GPUCompilationInfo = { + var messages(default,null) : ReadOnlyArray; +} + +enum abstract GPUCompilationMessageType(String) { + var error; + var warning; + var info; +} + +typedef GPUCompilationMessage = { + /** + * The human-readable, localizable text for this compilation message. + * Note: The {@link GPUCompilationMessage#message} should follow the best practices for language + * and direction information. This includes making use of any future standards which may + * emerge regarding the reporting of String language and direction metadata. + *

Editorial: + * At the time of this writing, no language/direction recommendation is available that provides + * compatibility and consistency with legacy APIs, but when there is, adopt it formally. + */ + var message(default,null) : String; + /** + * The severity level of the message. + * If the {@link GPUCompilationMessage#type} is {@link GPUCompilationMessageType#"error"}, it + * corresponds to a shader-creation error. + */ + var type(default,null) : GPUCompilationMessageType; + /** + * The line Float in the shader {@link GPUShaderModuleDescriptor#code} the + * {@link GPUCompilationMessage#message} corresponds to. Value is one-based, such that a lineNum of + * `1` indicates the first line of the shader {@link GPUShaderModuleDescriptor#code}. Lines are + * delimited by line breaks. + * If the {@link GPUCompilationMessage#message} corresponds to a substring this points to + * the line on which the substring begins. Must be `0` if the {@link GPUCompilationMessage#message} + * does not correspond to any specific point in the shader {@link GPUShaderModuleDescriptor#code}. + */ + var lineNum(default,null) : Int; + /** + * The offset, in UTF-16 code units, from the beginning of line {@link GPUCompilationMessage#lineNum} + * of the shader {@link GPUShaderModuleDescriptor#code} to the point or beginning of the substring + * that the {@link GPUCompilationMessage#message} corresponds to. Value is one-based, such that a + * {@link GPUCompilationMessage#linePos} of `1` indicates the first code unit of the line. + * If {@link GPUCompilationMessage#message} corresponds to a substring this points to the + * first UTF-16 code unit of the substring. Must be `0` if the {@link GPUCompilationMessage#message} + * does not correspond to any specific point in the shader {@link GPUShaderModuleDescriptor#code}. + */ + var linePos(default,null) : Int; + /** + * The offset from the beginning of the shader {@link GPUShaderModuleDescriptor#code} in UTF-16 + * code units to the point or beginning of the substring that {@link GPUCompilationMessage#message} + * corresponds to. Must reference the same position as {@link GPUCompilationMessage#lineNum} and + * {@link GPUCompilationMessage#linePos}. Must be `0` if the {@link GPUCompilationMessage#message} + * does not correspond to any specific point in the shader {@link GPUShaderModuleDescriptor#code}. + */ + var offset(default,null) : Int; + /** + * The Float of UTF-16 code units in the substring that {@link GPUCompilationMessage#message} + * corresponds to. If the message does not correspond with a substring then + * {@link GPUCompilationMessage#length} must be 0. + */ + var length(default,null) : Int; +} + + +typedef GPUShaderModuleCompilationHint = { + /** + * A {@link GPUPipelineLayout} that the {@link GPUShaderModule} may be used with in a future + * {@link GPUDevice#createComputePipeline()} or {@link GPUDevice#createRenderPipeline} call. + * If set to {@link GPUAutoLayoutMode#"auto"} the layout will be the [$default pipeline layout$] + * for the entry point associated with this hint will be used. + */ + var ?layout: GPUPipelineLayout; // | "auto" +} + +typedef GPUShaderModuleDescriptor = {> GPUObjectDescriptorBase, + /** + * The WGSL source code for the shader + * module. + */ + var code: String; + /** + * If defined MAY be interpreted as a source-map-v3 format. + * Source maps are optional, but serve as a standardized way to support dev-tool + * integration such as source-language debugging [[SourceMap]]. + * WGSL names (identifiers) in source maps follow the rules defined in WGSL identifier + * comparison. + */ + var ?sourceMap: Dynamic; + /** + * If defined maps an entry point name from the shader to a {@link GPUShaderModuleCompilationHint}. + * No validation is performed with any of these {@link GPUShaderModuleCompilationHint}. + * Implementations should use any information present in the {@link GPUShaderModuleCompilationHint} + * to perform as much compilation as is possible within {@link GPUDevice#createShaderModule}. + * Entry point names follow the rules defined in WGSL identifier comparison. + * Note: Supplying information in {@link GPUShaderModuleDescriptor#hints} does not have any + * observable effect, other than performance. Because a single shader module can hold + * multiple entry points, and multiple pipelines can be created from a single shader + * module, it can be more performant for an implementation to do as much compilation as + * possible once in {@link GPUDevice#createShaderModule} rather than multiple times in + * the multiple calls to {@link GPUDevice#createComputePipeline} / + * {@link GPUDevice#createRenderPipeline}. + */ + var ?hints: Dynamic; +} + +extern class GPUBindGroupLayout {} + +typedef GPUPipelineLayoutDescriptor = {> GPUObjectDescriptorBase, + /** + * A list of {@link GPUBindGroupLayout}s the pipline will use. Each element corresponds to a + * @group attribute in the {@link GPUShaderModule}, with the `N`th element corresponding with + * `@group(N)`. + */ + var bindGroupLayouts: Array; +} + +typedef GPUPipelineDescriptorBase = {> GPUObjectDescriptorBase, + var layout : GPUPipelineLayout; // | "auto" +} + +typedef GPUPipelineConstantValue = Float; + +typedef GPUProgrammableStage = { + var module: GPUShaderModule; + var entryPoint: String; + var ?constants: Dynamic; +} + +typedef GPUComputePipelineDescriptor = {> GPUPipelineDescriptorBase, + var compute: GPUProgrammableStage; +} + +enum abstract GPUVertexStepMode(String) { + var Vertex = "vertex"; + var Instance = "instance"; +} + +enum abstract GPUVertexFormat(String) { + var Uint8x2 = "uint8x2"; + var Uint8x4 = "uint8x4"; + var Sint8x2 = "sint8x2"; + var Sint8x4 = "sint8x4"; + var Unorm8x2 = "unorm8x2"; + var Unorm8x4 = "unorm8x4"; + var Snorm8x2 = "snorm8x2"; + var Snorm8x4 = "snorm8x4"; + var Uint16x2 = "uint16x2"; + var Uint16x4 = "uint16x4"; + var Sint16x2 = "sint16x2"; + var Sint16x4 = "sint16x4"; + var Unorm16x2 = "unorm16x2"; + var Unorm16x4 = "unorm16x4"; + var Snorm16x2 = "snorm16x2"; + var Snorm16x4 = "snorm16x4"; + var Float16x2 = "float16x2"; + var Float16x4 = "float16x4"; + var Float32 = "float32"; + var Float32x2 = "float32x2"; + var Float32x3 = "float32x3"; + var Float32x4 = "float32x4"; + var Uint32 = "uint32"; + var Uint32x2 = "uint32x2"; + var Uint32x3 = "uint32x3"; + var Uint32x4 = "uint32x4"; + var Sint32 = "sint32"; + var Sint32x2 = "sint32x2"; + var Sint32x3 = "sint32x3"; + var Sint32x4 = "sint32x4"; +} + +typedef GPUVertexAttribute = { + /** + * The {@link GPUVertexFormat} of the attribute. + */ + var format: GPUVertexFormat; + /** + * The offset, in bytes, from the beginning of the element to the data for the attribute. + */ + var offset: GPUSize64; + /** + * The numeric location associated with this attribute, which will correspond with a + * "@location" attribute + * declared in the {@link GPURenderPipelineDescriptor#vertex}.{@link GPUProgrammableStage#module|module}. + */ + var shaderLocation: GPUIndex32; +} + +typedef GPUVertexBufferLayout = { + /** + * The stride, in bytes, between elements of this array. + */ + var arrayStride: GPUSize64; + /** + * Whether each element of this array represents per-vertex data or per-instance data + */ + var ?stepMode: GPUVertexStepMode; + /** + * An array defining the layout of the vertex attributes within each element. + */ + var attributes: Array; +} + +typedef GPUVertexState = {> GPUProgrammableStage, + var ?buffers: Array>; +} + +enum abstract GPUBlendOperation(String) { + var Add = "add"; + var Subtract = "subtract"; + var Reverse_subtract = "reverse-subtract"; + var Min = "min"; + var Max = "max"; +} + +enum abstract GPUBlendFactor(String) { + var Zero = "zero"; + var One = "one"; + var Src = "src"; + var One_minus_src = "one-minus-src"; + var Src_alpha = "src-alpha"; + var One_minus_src_alpha = "one-minus-src-alpha"; + var Dst = "dst"; + var One_minus_dst = "one-minus-dst"; + var Dst_alpha = "dst-alpha"; + var One_minus_dst_alpha = "one-minus-dst-alpha"; + var Src_alpha_saturated = "src-alpha-saturated"; + var Constant = "constant"; + var One_minus_constant = "one-minus-constant"; +} + +typedef GPUBlendComponent = { + /** + * Defines the {@link GPUBlendOperation} used to calculate the values written to the target + * attachment components. + */ + var ?operation: GPUBlendOperation; + /** + * Defines the {@link GPUBlendFactor} operation to be performed on values from the fragment shader. + */ + var ?srcFactor: GPUBlendFactor; + /** + * Defines the {@link GPUBlendFactor} operation to be performed on values from the target attachment. + */ + var ?dstFactor: GPUBlendFactor; +} + + + +typedef GPUBlendState = { + var color: GPUBlendComponent; + var alpha: GPUBlendComponent; +} + +typedef GPUColorWriteFlags = Int; + +typedef GPUColorTargetState = { + var format: GPUTextureFormat; + var ?blend: GPUBlendState; + var ?writeMask: GPUColorWriteFlags; +} + +typedef GPUFragmentState = {>GPUProgrammableStage, + var targets: Array>; +} + +enum abstract GPUPrimitiveTopology(String) { + var Point_list = "point-list"; + var Line_list = "line-list"; + var Line_strip = "line-strip"; + var Triangle_list = "triangle-list"; + var Triangle_strip = "triangle-strip"; +} + +enum abstract GPUIndexFormat(String) { + var Uint16 = "uint16"; + var Uint32 = "uint32"; +} + +enum abstract GPUFrontFace(String) { + var Ccw = "ccw"; + var Cw = "cw"; +} + +enum abstract GPUCullMode(String) { + var None = "none"; + var Front = "front"; + var Back = "back"; +} + +typedef GPUPrimitiveState = { + /** + * The type of primitive to be constructed from the vertex inputs. + */ + var ?topology: GPUPrimitiveTopology; + /** + * For pipelines with strip topologies + * ({@link GPUPrimitiveTopology#"line-strip"} or {@link GPUPrimitiveTopology#"triangle-strip"}), + * this determines the index buffer format and primitive restart value + * ({@link GPUIndexFormat#"uint16"}/`0xFFFF` or {@link GPUIndexFormat#"uint32"}/`0xFFFFFFFF`). + * It is not allowed on pipelines with non-strip topologies. + * Note: Some implementations require knowledge of the primitive restart value to compile + * pipeline state objects. + * To use a strip-topology pipeline with an indexed draw call + * ({@link GPURenderCommandsMixin#drawIndexed()} or {@link GPURenderCommandsMixin#drawIndexedIndirect}), + * this must be set, and it must match the index buffer format used with the draw call + * (set in {@link GPURenderCommandsMixin#setIndexBuffer}). + * See [[#primitive-assembly]] for additional details. + */ + var ?stripIndexFormat: GPUIndexFormat; + /** + * Defines which polygons are considered front-facing. + */ + var ?frontFace: GPUFrontFace; + /** + * Defines which polygon orientation will be culled, if any. + */ + var ?cullMode: GPUCullMode; + /** + * If true, indicates that depth clipping is disabled. + * Requires the {@link GPUFeatureName#"depth-clip-control"} feature to be enabled. + */ + var ?unclippedDepth: Bool; +} + +enum abstract GPUCompareFunction(String) { + var Never = "never"; + var Less = "less"; + var Equal = "equal"; + var Less_equal = "less-equal"; + var Greater = "greater"; + var Not_equal = "not-equal"; + var Greater_equal = "greater-equal"; + var Always = "always"; +} + +enum abstract GPUStencilOperation(String) { + var Keep = "keep"; + var Zero = "zero"; + var Replace = "replace"; + var Invert = "invert"; + var Increment_clamp = "increment-clamp"; + var Decrement_clamp = "decrement-clamp"; + var Increment_wrap = "increment-wrap"; + var Decrement_wrap = "decrement-wrap"; +} + +typedef GPUStencilFaceState = { + /** + * The {@link GPUCompareFunction} used when testing fragments against + * {@link GPURenderPassDescriptor#depthStencilAttachment} stencil values. + */ + var ?compare: GPUCompareFunction; + /** + * The {@link GPUStencilOperation} performed if the fragment stencil comparison test described by + * {@link GPUStencilFaceState#compare} fails. + */ + var ?failOp: GPUStencilOperation; + /** + * The {@link GPUStencilOperation} performed if the fragment depth comparison described by + * {@link GPUDepthStencilState#depthCompare} fails. + */ + var ?depthFailOp: GPUStencilOperation; + /** + * The {@link GPUStencilOperation} performed if the fragment stencil comparison test described by + * {@link GPUStencilFaceState#compare} passes. + */ + var ?passOp: GPUStencilOperation; +} + +typedef GPUStencilValue = Int; +typedef GPUDepthBias = Float; + +typedef GPUDepthStencilState = { + /** + * The {@link GPUTextureViewDescriptor#format} of {@link GPURenderPassDescriptor#depthStencilAttachment} + * this {@link GPURenderPipeline} will be compatible with. + */ + var format: GPUTextureFormat; + /** + * Indicates if this {@link GPURenderPipeline} can modify + * {@link GPURenderPassDescriptor#depthStencilAttachment} depth values. + */ + var ?depthWriteEnabled: Bool; + /** + * The comparison operation used to test fragment depths against + * {@link GPURenderPassDescriptor#depthStencilAttachment} depth values. + */ + var ?depthCompare: GPUCompareFunction; + /** + * Defines how stencil comparisons and operations are performed for front-facing primitives. + */ + var ?stencilFront: GPUStencilFaceState; + /** + * Defines how stencil comparisons and operations are performed for back-facing primitives. + */ + var ?stencilBack: GPUStencilFaceState; + /** + * Bitmask controlling which {@link GPURenderPassDescriptor#depthStencilAttachment} stencil value + * bits are read when performing stencil comparison tests. + */ + var ?stencilReadMask: GPUStencilValue; + /** + * Bitmask controlling which {@link GPURenderPassDescriptor#depthStencilAttachment} stencil value + * bits are written to when performing stencil operations. + */ + var ?stencilWriteMask: GPUStencilValue; + /** + * Constant depth bias added to each fragment. See [$biased fragment depth$] for details. + */ + var ?depthBias: GPUDepthBias; + /** + * Depth bias that scales with the fragment’s slope. See [$biased fragment depth$] for details. + */ + var ?depthBiasSlopeScale: Float; + /** + * The maximum depth bias of a fragment. See [$biased fragment depth$] for details. + */ + var ?depthBiasClamp: Float; +} + +typedef GPUSampleMask = Int; + +typedef GPUMultisampleState = { + /** + * Number of samples per pixel. This {@link GPURenderPipeline} will be compatible only + * with attachment textures ({@link GPURenderPassDescriptor#colorAttachments} + * and {@link GPURenderPassDescriptor#depthStencilAttachment}) + * with matching {@link GPUTextureDescriptor#sampleCount}s. + */ + var ?count: GPUSize32; + /** + * Mask determining which samples are written to. + */ + var ?mask: GPUSampleMask; + /** + * When `true` indicates that a fragment's alpha channel should be used to generate a sample + * coverage mask. + */ + var ?alphaToCoverageEnabled: Bool; +} + +typedef GPURenderPipelineDescriptor = {> GPUPipelineDescriptorBase, + /** + * Describes the vertex shader entry point of the pipeline and its input buffer layouts. + */ + var vertex: GPUVertexState; + /** + * Describes the primitive-related properties of the pipeline. + */ + var ?primitive: GPUPrimitiveState; + /** + * Describes the optional depth-stencil properties, including the testing, operations, and bias. + */ + var ?depthStencil: GPUDepthStencilState; + /** + * Describes the multi-sampling properties of the pipeline. + */ + var ?multisample: GPUMultisampleState; + /** + * Describes the fragment shader entry point of the pipeline and its output colors. If + * `Void`, the [[#no-color-output]] mode is enabled. + */ + var ?fragment: GPUFragmentState; +} + + +extern class GPUPipelineLayout {} + +extern class GPUPipelineBase { + function getBindGroupLayout( index: Int ) : GPUBindGroupLayout; +} +extern class GPUComputePipeline extends GPUPipelineBase {} +extern class GPURenderPipeline extends GPUPipelineBase {} + +typedef GPUCommandEncoderDescriptor = {> GPUObjectDescriptorBase, } + +extern class GPUDebugCommands { + /** + * Begins a labeled debug group containing subsequent commands. + * @param groupLabel - The label for the command group. + */ + function pushDebugGroup( groupLabel: String ) : Void; + /** + * Ends the labeled debug group most recently started by {@link GPUDebugCommandsMixin#pushDebugGroup}. + */ + function popDebugGroup() : Void; + /** + * Marks a point in a stream of commands with a label. + * @param markerLabel - The label to insert. + */ + function insertDebugMarker( markerLabel: String ) : Void; +} + +extern class GPUBindGroup {} + +typedef GPUBufferDynamicOffset = Int; + +extern class GPUBindingCommands extends GPUDebugCommands { + function setBindGroup( index: GPUIndex32, bindGroup: GPUBindGroup, ?dynamicOffsets: Dynamic, ?dynamicOffsetsDataStart: GPUSize64, ?dynamicOffsetsDataLength: GPUSize32 ) : Void; +} + +extern class GPURenderCommands extends GPUBindingCommands { + /** + * Sets the current {@link GPURenderPipeline}. + * @param pipeline - The render pipeline to use for subsequent drawing commands. + */ + function setPipeline( pipeline: GPURenderPipeline ) : Void; + /** + * Sets the current index buffer. + * @param buffer - Buffer containing index data to use for subsequent drawing commands. + * @param indexFormat - Format of the index data contained in `buffer`. + * @param offset - Offset in bytes into `buffer` where the index data begins. Defaults to `0`. + * @param size - Size in bytes of the index data in `buffer`. + * Defaults to the size of the buffer minus the offset. + */ + function setIndexBuffer( buffer: GPUBuffer, indexFormat: GPUIndexFormat, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + /** + * Sets the current vertex buffer for the given slot. + * @param slot - The vertex buffer slot to set the vertex buffer for. + * @param buffer - Buffer containing vertex data to use for subsequent drawing commands. + * @param offset - Offset in bytes into `buffer` where the vertex data begins. Defaults to `0`. + * @param size - Size in bytes of the vertex data in `buffer`. + * Defaults to the size of the buffer minus the offset. + */ + function setVertexBuffer( slot: GPUIndex32, buffer: GPUBuffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + /** + * Draws primitives. + * See [[#rendering-operations]] for the detailed specification. + * @param vertexCount - The Float of vertices to draw. + * @param instanceCount - The Float of instances to draw. + * @param firstVertex - Offset into the vertex buffers, in vertices, to begin drawing from. + * @param firstInstance - First instance to draw. + */ + function draw( vertexCount: GPUSize32, ?instanceCount: GPUSize32, ?firstVertex: GPUSize32, ?firstInstance: GPUSize32 ) : Void; + function drawIndexed( indexCount: GPUSize32, ?instanceCount: GPUSize32, ?firstIndex: GPUSize32, ?baseVertex: GPUSignedOffset32, ?firstInstance: GPUSize32 ) : Void; + function drawIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; + function drawIndexedIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; +} + +typedef GPUColor = { + var r: Float; + var g: Float; + var b: Float; + var a: Float; +} + +enum abstract GPULoadOp(String) { + var Load = "load"; + var Clear = "clear"; +} + + +enum abstract GPUStoreOp(String) { + var Store = "store"; + var Discard = "discard"; +} + +typedef GPURenderPassColorAttachment = { + /** + * A {@link GPUTextureView} describing the texture subresource that will be output to for this + * color attachment. + */ + var view: GPUTextureView; + /** + * A {@link GPUTextureView} describing the texture subresource that will receive the resolved + * output for this color attachment if {@link GPURenderPassColorAttachment#view} is + * multisampled. + */ + var ?resolveTarget: GPUTextureView; + /** + * Indicates the value to clear {@link GPURenderPassColorAttachment#view} to prior to executing the + * render pass. If not map/exist|provided, defaults to `{r: 0, g: 0, b: 0, a: 0}`. Ignored + * if {@link GPURenderPassColorAttachment#loadOp} is not {@link GPULoadOp#"clear"}. + * The components of {@link GPURenderPassColorAttachment#clearValue} are all double values. + * They are converted [$to a texel value of texture format$] matching the render attachment. + * If conversion fails, a validation error is generated. + */ + var ?clearValue: GPUColor; + /** + * Indicates the load operation to perform on {@link GPURenderPassColorAttachment#view} prior to + * executing the render pass. + * Note: It is recommended to prefer clearing; see {@link GPULoadOp#"clear"} for details. + */ + var loadOp: GPULoadOp; + /** + * The store operation to perform on {@link GPURenderPassColorAttachment#view} + * after executing the render pass. + */ + var storeOp: GPUStoreOp; +} + +typedef GPURenderPassDepthStencilAttachment = { + /** + * A {@link GPUTextureView} describing the texture subresource that will be output to + * and read from for this depth/stencil attachment. + */ + var view: GPUTextureView; + /** + * Indicates the value to clear {@link GPURenderPassDepthStencilAttachment#view}'s depth component + * to prior to executing the render pass. Ignored if {@link GPURenderPassDepthStencilAttachment#depthLoadOp} + * is not {@link GPULoadOp#"clear"}. Must be between 0.0 and 1.0, inclusive. + * + */ + var ?depthClearValue: Float; + /** + * Indicates the load operation to perform on {@link GPURenderPassDepthStencilAttachment#view}'s + * depth component prior to executing the render pass. + * Note: It is recommended to prefer clearing; see {@link GPULoadOp#"clear"} for details. + */ + var ?depthLoadOp: GPULoadOp; + /** + * The store operation to perform on {@link GPURenderPassDepthStencilAttachment#view}'s + * depth component after executing the render pass. + */ + var ?depthStoreOp: GPUStoreOp; + /** + * Indicates that the depth component of {@link GPURenderPassDepthStencilAttachment#view} + * is read only. + */ + var ?depthReadOnly: Bool; + /** + * Indicates the value to clear {@link GPURenderPassDepthStencilAttachment#view}'s stencil component + * to prior to executing the render pass. Ignored if {@link GPURenderPassDepthStencilAttachment#stencilLoadOp} + * is not {@link GPULoadOp#"clear"}. + * The value will be converted to the type of the stencil aspect of `view` by taking the same + * Float of LSBs as the Float of bits in the stencil aspect of one texel block of `view`. + */ + var ?stencilClearValue: GPUStencilValue; + /** + * Indicates the load operation to perform on {@link GPURenderPassDepthStencilAttachment#view}'s + * stencil component prior to executing the render pass. + * Note: It is recommended to prefer clearing; see {@link GPULoadOp#"clear"} for details. + */ + var ?stencilLoadOp: GPULoadOp; + /** + * The store operation to perform on {@link GPURenderPassDepthStencilAttachment#view}'s + * stencil component after executing the render pass. + */ + var ?stencilStoreOp: GPUStoreOp; + /** + * Indicates that the stencil component of {@link GPURenderPassDepthStencilAttachment#view} + * is read only. + */ + var ?stencilReadOnly: Bool; +} + +enum abstract GPURenderPassTimestampLocation(String) { + var Beginning = "beginning"; + var End = "end"; +} + +typedef GPURenderPassTimestampWrite = { + var querySet: GPUQuerySet; + var queryIndex: GPUSize32; + var location: GPURenderPassTimestampLocation; +} + +typedef GPURenderPassTimestampWrites = Array; + +typedef GPURenderPassDescriptor = {>GPUObjectDescriptorBase, + /** + * The set of {@link GPURenderPassColorAttachment} values in this sequence defines which + * color attachments will be output to when executing this render pass. + * Due to compatible usage list|usage compatibility, no color attachment + * may alias another attachment or any resource used inside the render pass. + */ + var colorAttachments: Array>; + /** + * The {@link GPURenderPassDepthStencilAttachment} value that defines the depth/stencil + * attachment that will be output to and tested against when executing this render pass. + * Due to compatible usage list|usage compatibility, no writable depth/stencil attachment + * may alias another attachment or any resource used inside the render pass. + */ + var ?depthStencilAttachment: GPURenderPassDepthStencilAttachment; + /** + * The {@link GPUQuerySet} value defines where the occlusion query results will be stored for this pass. + */ + var ?occlusionQuerySet: GPUQuerySet; + /** + * A sequence of {@link GPURenderPassTimestampWrite} values defines where and when timestamp values will be written for this pass. + */ + var ?timestampWrites: GPURenderPassTimestampWrites; + /** + * The maximum Float of draw calls that will be done in the render pass. Used by some + * implementations to size work injected before the render pass. Keeping the default value + * is a good default, unless it is known that more draw calls will be done. + */ + var ?maxDrawCount: GPUSize64; +} + +extern class GPURenderPassEncoder extends GPURenderCommands { + /** + * Sets the viewport used during the rasterization stage to linearly map from normalized device + * coordinates to viewport coordinates. + * @param x - Minimum X value of the viewport in pixels. + * @param y - Minimum Y value of the viewport in pixels. + * @param width - Width of the viewport in pixels. + * @param height - Height of the viewport in pixels. + * @param minDepth - Minimum depth value of the viewport. + * @param maxDepth - Maximum depth value of the viewport. + */ + function setViewport( x: Int, y: Int, width: Int, height: Int, minDepth: Float, maxDepth: Float ) : Void; + /** + * Sets the scissor rectangle used during the rasterization stage. + * After transformation into viewport coordinates any fragments which fall outside the scissor + * rectangle will be discarded. + * @param x - Minimum X value of the scissor rectangle in pixels. + * @param y - Minimum Y value of the scissor rectangle in pixels. + * @param width - Width of the scissor rectangle in pixels. + * @param height - Height of the scissor rectangle in pixels. + */ + function setScissorRect( x: GPUIntegerCoordinate, y: GPUIntegerCoordinate, width: GPUIntegerCoordinate, height: GPUIntegerCoordinate ) : Void; + /** + * Sets the constant blend color and alpha values used with {@link GPUBlendFactor#"constant"} + * and {@link GPUBlendFactor#"one-minus-constant"} {@link GPUBlendFactor}s. + * @param color - The color to use when blending. + */ + function setBlendConstant( color: GPUColor ) : Void; + /** + * Sets the {@link RenderState#[[stencilReference]]} value used during stencil tests with + * the {@link GPUStencilOperation#"replace"} {@link GPUStencilOperation}. + * @param reference - The new stencil reference value. + */ + function setStencilReference( reference: GPUStencilValue ) : Void; + /** + * @param queryIndex - The index of the query in the query set. + */ + function beginOcclusionQuery( queryIndex: GPUSize32 ) : Void; + /** + */ + function endOcclusionQuery() : Void; + /** + * Executes the commands previously recorded into the given {@link GPURenderBundle}s as part of + * this render pass. + * When a {@link GPURenderBundle} is executed, it does not inherit the render pass's pipeline, bind + * groups, or vertex and index buffers. After a {@link GPURenderBundle} has executed, the render + * pass's pipeline, bind group, and vertex/index buffer state is cleared + * (to the initial, empty values). + * Note: The state is cleared, not restored to the previous state. + * This occurs even if zero {@link GPURenderBundle|GPURenderBundles} are executed. + * @param bundles - List of render bundles to execute. + */ + function executeBundles( bundles: Array ) : Void; + /** + * Completes recording of the render pass commands sequence. + */ + function end() : Void; +} + +extern class GPURenderBundle {} + +typedef GPUImageCopyBuffer = {> GPUImageDataLayout, + var ?offset : GPUSize64; + var bytesPerRow : GPUSize32; + var rowsPerImage : GPUSize32; +} + +typedef GPUCommandBufferDescriptor = GPUObjectDescriptorBase; + +enum abstract GPUComputePassTimestampLocation(String) { + var Beginning = "beginning"; + var End = "end"; +} + +typedef GPUComputePassTimestampWrite = { + var querySet: GPUQuerySet; + var queryIndex: GPUSize32; + var location: GPUComputePassTimestampLocation; +} + +typedef GPUComputePassTimestampWrites = Array; + +typedef GPUComputePassDescriptor = {> GPUObjectDescriptorBase, + /** + * A sequence of {@link GPUComputePassTimestampWrite} values define where and when timestamp values will be written for this pass. + */ + var ?timestampWrites: GPUComputePassTimestampWrites; +} + +extern class GPUComputePassEncoder extends GPUBindingCommands { + /** + * Sets the current {@link GPUComputePipeline}. + * @param pipeline - The compute pipeline to use for subsequent dispatch commands. + */ + function setPipeline( pipeline: GPUComputePipeline ) : Void; + /** + * Dispatch work to be performed with the current {@link GPUComputePipeline}. + * See [[#computing-operations]] for the detailed specification. + * @param workgroupCountX - X dimension of the grid of workgroups to dispatch. + * @param workgroupCountY - Y dimension of the grid of workgroups to dispatch. + * @param workgroupCountZ - Z dimension of the grid of workgroups to dispatch. + */ + function dispatchWorkgroups( workgroupCountX: GPUSize32, ?workgroupCountY: GPUSize32, ?workgroupCountZ: GPUSize32 ) : Void; + /** + * Dispatch work to be performed with the current {@link GPUComputePipeline} using parameters read + * from a {@link GPUBuffer}. + * See [[#computing-operations]] for the detailed specification. + * packed block of **three 32-bit unsigned integer values (12 bytes total)**, + * given in the same order as the arguments for {@link GPUComputePassEncoder#dispatchWorkgroups}. + * For example: + * @param indirectBuffer - Buffer containing the indirect dispatch parameters. + * @param indirectOffset - Offset in bytes into `indirectBuffer` where the dispatch data begins. + */ + function dispatchWorkgroupsIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; + /** + * Completes recording of the compute pass commands sequence. + */ + function end() : Void; +} + +extern class GPUCommandEncoder extends GPUDebugCommands { + /** + * Begins encoding a render pass described by `descriptor`. + * @param descriptor - Description of the {@link GPURenderPassEncoder} to create. + */ + function beginRenderPass( descriptor: GPURenderPassDescriptor ) : GPURenderPassEncoder; + /** + * Begins encoding a compute pass described by `descriptor`. + * descriptor: + */ + function beginComputePass( ?descriptor: GPUComputePassDescriptor ) : GPUComputePassEncoder; + /** + * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of a + * {@link GPUBuffer} to a sub-region of another {@link GPUBuffer}. + * @param source - The {@link GPUBuffer} to copy from. + * @param sourceOffset - Offset in bytes into `source` to begin copying from. + * @param destination - The {@link GPUBuffer} to copy to. + * @param destinationOffset - Offset in bytes into `destination` to place the copied data. + * @param size - Bytes to copy. + */ + function copyBufferToBuffer( source: GPUBuffer, sourceOffset: GPUSize64, destination: GPUBuffer, destinationOffset: GPUSize64, size: GPUSize64 ) : Void; + /** + * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of a + * {@link GPUBuffer} to a sub-region of one or multiple continuous texture subresources. + * @param source - Combined with `copySize`, defines the region of the source buffer. + * @param destination - Combined with `copySize`, defines the region of the destination texture subresource. + * `copySize`: + */ + function copyBufferToTexture( source: GPUImageCopyBuffer, destination: GPUImageCopyTexture, copySize: GPUExtent3D ) : Void; + /** + * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of one or + * multiple continuous texture subresourcesto a sub-region of a {@link GPUBuffer}. + * @param source - Combined with `copySize`, defines the region of the source texture subresources. + * @param destination - Combined with `copySize`, defines the region of the destination buffer. + * `copySize`: + */ + function copyTextureToBuffer( source: GPUImageCopyTexture, destination: GPUImageCopyBuffer, copySize: GPUExtent3D ) : Void; + /** + * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of one + * or multiple contiguous texture subresources to another sub-region of one or + * multiple continuous texture subresources. + * @param source - Combined with `copySize`, defines the region of the source texture subresources. + * @param destination - Combined with `copySize`, defines the region of the destination texture subresources. + * `copySize`: + */ + function copyTextureToTexture( source: GPUImageCopyTexture, destination: GPUImageCopyTexture, copySize: GPUExtent3D ) : Void; + /** + * Encode a command into the {@link GPUCommandEncoder} that fills a sub-region of a + * {@link GPUBuffer} with zeros. + * @param buffer - The {@link GPUBuffer} to clear. + * @param offset - Offset in bytes into `buffer` where the sub-region to clear begins. + * @param size - Size in bytes of the sub-region to clear. Defaults to the size of the buffer minus `offset`. + */ + function clearBuffer( buffer: GPUBuffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + /** + * Writes a timestamp value into a querySet when all previous commands have completed executing. + * @param querySet - The query set that will store the timestamp values. + * @param queryIndex - The index of the query in the query set. + */ + function writeTimestamp( querySet: GPUQuerySet, queryIndex: GPUSize32 ) : Void; + /** + * Resolves query results from a {@link GPUQuerySet} out into a range of a {@link GPUBuffer}. + * querySet: + * firstQuery: + * queryCount: + * destination: + * destinationOffset: + */ + function resolveQuerySet( querySet: GPUQuerySet, firstQuery: GPUSize32, queryCount: GPUSize32, destination: GPUBuffer, destinationOffset: GPUSize64 ) : Void; + /** + * Completes recording of the commands sequence and returns a corresponding {@link GPUCommandBuffer}. + * descriptor: + */ + function finish( ?descriptor: GPUCommandBufferDescriptor ) : GPUCommandBuffer; +} + +enum abstract GPUQueryType(String) { + var Occlusion = "occlusion"; + var Timestamp = "timestamp"; +} + +extern class GPUQuerySet { + /** + * Destroys the {@link GPUQuerySet}. + */ + function destroy() : Void; + /** + * The type of the queries managed by this {@link GPUQuerySet}. + */ + var type(default,null) : GPUQueryType; + /** + * The Float of queries managed by this {@link GPUQuerySet}. + */ + var count(default,null) : GPUSize32; +} + +enum abstract GPUAddressMode(String) { + var Clamp_to_edge = "clamp-to-edge"; + var Repeat = "repeat"; + var Mirror_repeat = "mirror-repeat"; +} + +enum abstract GPUFilterMode(String) { + var Nearest = "nearest"; + var Linear = "linear"; +} + +enum abstract GPUMipmapFilterMode(String) { + var Nearest = "nearest"; + var Linear = "linear"; +} + +typedef GPUSamplerDescriptor = {> + /** + */ + var ?addressModeU: GPUAddressMode; + /** + */ + var ?addressModeV: GPUAddressMode; + /** + * Specifies the {{GPUAddressMode|address modes}} for the texture width, height, and depth + * coordinates, respectively. + */ + var ?addressModeW: GPUAddressMode; + /** + * Specifies the sampling behavior when the sample footprint is smaller than or equal to one + * texel. + */ + var ?magFilter: GPUFilterMode; + /** + * Specifies the sampling behavior when the sample footprint is larger than one texel. + */ + var ?minFilter: GPUFilterMode; + /** + * Specifies behavior for sampling between mipmap levels. + */ + var ?mipmapFilter: GPUMipmapFilterMode; + /** + */ + var ?lodMinClamp: Int; + /** + * Specifies the minimum and maximum levels of detail, respectively, used internally when + * sampling a texture. + */ + var ?lodMaxClamp: Int; + /** + * When provided the sampler will be a comparison sampler with the specified + * {@link GPUCompareFunction}. + * Note: Comparison samplers may use filtering, but the sampling results will be + * implementation-dependent and may differ from the normal filtering rules. + */ + var ?compare: GPUCompareFunction; + /** + * Specifies the maximum anisotropy value clamp used by the sampler. + * Note: Most implementations support {@link GPUSamplerDescriptor#maxAnisotropy} values in range + * between 1 and 16, inclusive. The used value of {@link GPUSamplerDescriptor#maxAnisotropy} will + * be clamped to the maximum value that the platform supports. + */ + var ?maxAnisotropy: Int; +} + +extern class GPUSampler {} + + +typedef GPUBufferBinding = { + /** + * The {@link GPUBuffer} to bind. + */ + var buffer: GPUBuffer; + /** + * The offset, in bytes, from the beginning of {@link GPUBufferBinding#buffer} to the + * beginning of the range exposed to the shader by the buffer binding. + */ + var ?offset: GPUSize64; + /** + * The size, in bytes, of the buffer binding. If `Void`, specifies the range starting at + * {@link GPUBufferBinding#offset} and ending at the end of {@link GPUBufferBinding#buffer}. + */ + var ?size: GPUSize64; +} + +extern class GPUExternalTexture {} + +typedef GPUBindingResource = haxe.ds.Either>>; + + +typedef GPUBindGroupEntry = { + /** + * A unique identifier for a resource binding within the {@link GPUBindGroup}, corresponding to a + * {@link GPUBindGroupLayoutEntry#binding|GPUBindGroupLayoutEntry.binding} and a @binding + * attribute in the {@link GPUShaderModule}. + */ + var binding: GPUIndex32; + /** + * The resource to bind, which may be a {@link GPUSampler}, {@link GPUTextureView}, + * {@link GPUExternalTexture}, or {@link GPUBufferBinding}. + */ + var resource: GPUBindingResource; +} + +typedef GPUBindGroupDescriptor = {> GPUObjectDescriptorBase, + /** + * The {@link GPUBindGroupLayout} the entries of this bind group will conform to. + */ + var layout: GPUBindGroupLayout; + /** + * A list of entries describing the resources to expose to the shader for each binding + * described by the {@link GPUBindGroupDescriptor#layout}. + */ + var entries: Array; +} + +enum GPUShaderStage { + VERTEX; + FRAGMENT; + COMPUTE; +} + +typedef GPUShaderStageFlags = haxe.EnumFlags; + +enum abstract GPUBufferBindingType(String) { + var Uniform = "uniform"; + var Storage = "storage"; + var Read_only_storage = "read-only-storage"; +} + +typedef GPUBufferBindingLayout = { + /** + * Indicates the type required for buffers bound to this bindings. + */ + var ?type: GPUBufferBindingType; + /** + * Indicates whether this binding requires a dynamic offset. + */ + var ?hasDynamicOffset: Bool; + /** + * Indicates the minimum {@link GPUBufferBinding#size} of a buffer binding used with this bind point. + * Bindings are always validated against this size in {@link GPUDevice#createBindGroup}. + * If this *is not* `0`, pipeline creation additionally [$validating shader binding|validates$] + * that this value ≥ the minimum buffer binding size of the variable. + * If this *is* `0`, it is ignored by pipeline creation, and instead draw/dispatch commands + * [$Validate encoder bind groups|validate$] that each binding in the {@link GPUBindGroup} + * satisfies the minimum buffer binding size of the variable. + * Note: + * Similar execution-time validation is theoretically possible for other + * binding-related fields specified for early validation, like + * {@link GPUTextureBindingLayout#sampleType} and {@link GPUStorageTextureBindingLayout#format}, + * which currently can only be validated in pipeline creation. + * However, such execution-time validation could be costly or unnecessarily complex, so it is + * available only for {@link GPUBufferBindingLayout#minBindingSize} which is expected to have the + * most ergonomic impact. + */ + var ?minBindingSize: GPUSize64; +} + +enum abstract GPUSamplerBindingType(String) { + var Filtering = "filtering"; + var Non_filtering = "non-filtering"; + var Comparison = "comparison"; +} + +typedef GPUSamplerBindingLayout = { + /** + * Indicates the required type of a sampler bound to this bindings. + */ + var ?type: GPUSamplerBindingType; +} + +enum abstract GPUTextureSampleType(String) { + var Float = "float"; + var Unfilterable_float = "unfilterable-float"; + var Depth = "depth"; + var Sint = "sint"; + var Uint = "uint"; +} + +typedef GPUTextureBindingLayout = { + /** + * Indicates the type required for texture views bound to this binding. + */ + var ?sampleType: GPUTextureSampleType; + /** + * Indicates the required {@link GPUTextureViewDescriptor#dimension} for texture views bound to + * this binding. + */ + var ?viewDimension: GPUTextureViewDimension; + /** + * Indicates whether or not texture views bound to this binding must be multisampled. + */ + var ?multisampled: Bool; +} + +enum abstract GPUStorageTextureAccess(String) { + var Write_only = "write_only"; +} + +typedef GPUStorageTextureBindingLayout = { + /** + * Indicates whether texture views bound to this binding will be bound for read-only or + * write-only access. + */ + var ?access: GPUStorageTextureAccess; + /** + * The required {@link GPUTextureViewDescriptor#format} of texture views bound to this binding. + */ + var format: GPUTextureFormat; + /** + * Indicates the required {@link GPUTextureViewDescriptor#dimension} for texture views bound to + * this binding. + */ + var ?viewDimension: GPUTextureViewDimension; +} + +typedef GPUExternalTextureBindingLayout = {} + +typedef GPUBindGroupLayoutEntry = { + /** + * A unique identifier for a resource binding within the {@link GPUBindGroupLayout}, corresponding + * to a {@link GPUBindGroupEntry#binding|GPUBindGroupEntry.binding} and a @binding + * attribute in the {@link GPUShaderModule}. + */ + var binding: GPUIndex32; + /** + * A bitset of the members of {@link GPUShaderStage}. + * Each set bit indicates that a {@link GPUBindGroupLayoutEntry}'s resource + * will be accessible from the associated shader stage. + */ + var visibility: GPUShaderStageFlags; + /** + * When not `Void`, indicates the binding resource type for this {@link GPUBindGroupLayoutEntry} + * is {@link GPUBufferBinding}. + */ + var ?buffer: GPUBufferBindingLayout; + /** + * When not `Void`, indicates the binding resource type for this {@link GPUBindGroupLayoutEntry} + * is {@link GPUSampler}. + */ + var ?sampler: GPUSamplerBindingLayout; + /** + * When not `Void`, indicates the binding resource type for this {@link GPUBindGroupLayoutEntry} + * is {@link GPUTextureView}. + */ + var ?texture: GPUTextureBindingLayout; + /** + * When not `Void`, indicates the binding resource type for this {@link GPUBindGroupLayoutEntry} + * is {@link GPUTextureView}. + */ + var ?storageTexture: GPUStorageTextureBindingLayout; + /** + * When not `Void`, indicates the binding resource type for this {@link GPUBindGroupLayoutEntry} + * is {@link GPUExternalTexture}. + */ + var ?externalTexture: GPUExternalTextureBindingLayout; +} + +typedef GPUBindGroupLayoutDescriptor = {> GPUObjectDescriptorBase, + var entries: Array; +} + +typedef GPUQuerySetDescriptor = {> GPUObjectDescriptorBase, + /** + * The type of queries managed by {@link GPUQuerySet}. + */ + var type: GPUQueryType; + /** + * The Float of queries managed by {@link GPUQuerySet}. + */ + var count: GPUSize32; +} + +extern class GPUDevice { + /** + * A set containing the {@link GPUFeatureName} values of the features + * supported by the device (i.e. the ones with which it was created). + */ + var features(default,null) : GPUSupportedFeatures; + /** + * Exposes the limits supported by the device + * (which are exactly the ones with which it was created). + */ + var limits(default,null) : GPUSupportedLimits; + /** + * The primary {@link GPUQueue} for this device. + */ + var queue(default,null) : GPUQueue; + /** + * Destroys the device, preventing further operations on it. + * Outstanding asynchronous operations will fail. + * Note: It is valid to destroy a device multiple times. + */ + function destroy() : Void; + + function createBuffer( descriptor: GPUBufferDescriptor ) : GPUBuffer; + function createTexture( descriptor: GPUTextureDescriptor ) : GPUTexture; + function createSampler( ?descriptor: GPUSamplerDescriptor ) : GPUSampler; + //function importExternalTexture( descriptor: GPUExternalTextureDescriptor ) : GPUExternalTexture; + function createPipelineLayout( descriptor: GPUPipelineLayoutDescriptor ) : GPUPipelineLayout; + function createBindGroup( descriptor: GPUBindGroupDescriptor ) : GPUBindGroup; + function createBindGroupLayout( descriptor: GPUBindGroupLayoutDescriptor ) : GPUBindGroupLayout; + function createShaderModule( descriptor: GPUShaderModuleDescriptor ) : GPUShaderModule; + function createComputePipeline( descriptor : GPUComputePipelineDescriptor ) : GPUComputePipeline; + function createRenderPipeline( descriptor : GPURenderPipelineDescriptor ) : GPURenderPipeline; + function createComputePipelineAsync( descriptor : GPUComputePipelineDescriptor ) : Promise; + function createRenderPipelineAsync( descriptor : GPURenderPipelineDescriptor ) : Promise; + function createCommandEncoder( ?descriptor: GPUCommandEncoderDescriptor ) : GPUCommandEncoder; + //function createRenderBundleEncoder( descriptor : GPURenderBundleEncoderDescriptor ) : GPURenderBundleEncoder; + function createQuerySet( descriptor : GPUQuerySetDescriptor ) : GPUQuerySet; + var lost(default,null) : Promise; + function pushErrorScope( filter : GPUErrorFilter ) : Void; + function popErrorScope() : Promise>; + //onuncapturederror : EventHandler; +} + + +enum abstract GPUCanvasAlphaMode(String) { + var Opaque = "opaque"; + var Premultiplied = "premultiplied"; +} + +typedef GPUCanvasConfiguration = { + /** + * The {@link GPUDevice} that textures returned by {@link GPUCanvasContext#getCurrentTexture} will be + * compatible with. + */ + var device: GPUDevice; + /** + * The format that textures returned by {@link GPUCanvasContext#getCurrentTexture} will have. + * Must be one of the Supported context formats. + */ + var format: GPUTextureFormat; + /** + * The usage that textures returned by {@link GPUCanvasContext#getCurrentTexture} will have. + * {@link GPUTextureUsage#RENDER_ATTACHMENT} is the default, but is not automatically included + * if the usage is explicitly set. Be sure to include {@link GPUTextureUsage#RENDER_ATTACHMENT} + * when setting a custom usage if you wish to use textures returned by + * {@link GPUCanvasContext#getCurrentTexture} as color targets for a render pass. + */ + var ?usage: GPUTextureUsageFlags; + /** + * The formats that views created from textures returned by + * {@link GPUCanvasContext#getCurrentTexture} may use. + */ + var ?viewFormats: Array; + /** + * The color space that values written into textures returned by + * {@link GPUCanvasContext#getCurrentTexture} should be displayed with. + */ + var ?colorSpace: PredefinedColorSpace; + /** + * Determines the effect that alpha values will have on the content of textures returned by + * {@link GPUCanvasContext#getCurrentTexture} when read, displayed, or used as an image source. + */ + var ?alphaMode: GPUCanvasAlphaMode; +} + +extern class GPUCanvasContext { + var canvas(default,null) : js.html.CanvasElement; + function configure( configuration: GPUCanvasConfiguration ) : Void; + function unconfigure() : Void; + /** + * Get the {@link GPUTexture} that will be composited to the document by the {@link GPUCanvasContext} + * next. + * Note: The same {@link GPUTexture} object will be returned by every + * call to {@link GPUCanvasContext#getCurrentTexture} made within the same frame (i.e. between + * invocations of "[$update the rendering of the WebGPU canvas$]"), even if that {@link GPUTexture} + * is destroyed, failed validation, or failed to allocate, **unless** the current texture has + * been removed (in [$Replace the drawing buffer$]). + */ + function getCurrentTexture() : GPUTexture; +} \ No newline at end of file diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx new file mode 100644 index 0000000000..73b0c84c2f --- /dev/null +++ b/h3d/impl/WebGpuDriver.hx @@ -0,0 +1,176 @@ +package h3d.impl; + +import h3d.impl.Driver; +import h3d.impl.WebGpuApi; +import h3d.mat.Pass; + +class WebGpuDriver extends h3d.impl.Driver { + + var canvas : js.html.CanvasElement; + var context : GPUCanvasContext; + var device : GPUDevice; + + var colorTex : GPUTexture; + var depthTex : GPUTexture; + var colorView : GPUTextureView; + var depthView : GPUTextureView; + var commandList : GPUCommandEncoder; + var renderPass : GPURenderPassEncoder; + var renderPassDesc : GPURenderPassDescriptor; + var needClear : Bool; + + public function new() { + inst = this; + } + + override function isDisposed() { + return false; + } + + override function init( onCreate : Bool -> Void, forceSoftware = false ) { + var gpu = GPU.get(); + if( gpu == null ) + throw "Device does not support WebGPU"; + gpu.requestAdapter().then(function(adapt) { + adapt.requestDevice().then(function(device) { + this.device = device; + canvas = @:privateAccess hxd.Window.getInstance().canvas; + context = cast canvas.getContext("webgpu"); + resize(canvas.width, canvas.height); + beginFrame(); + onCreate(false); + }); + }); + } + + override function resize(width:Int, height:Int) { + if( canvas.style.width == "" ) { + canvas.style.width = Std.int(width / js.Browser.window.devicePixelRatio)+"px"; + canvas.style.height = Std.int(height / js.Browser.window.devicePixelRatio)+"px"; + } + canvas.width = width; + canvas.height = height; + + context.configure({ + device : device, + format : Bgra8unorm, + usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC, + alphaMode : Opaque, + }); + + colorTex = context.getCurrentTexture(); + colorView = colorTex.createView(); + depthTex = device.createTexture({ + size : { width : width, height : height }, + dimension : D2, + format : Depth24plus_stencil8, + usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC, + }); + depthView = depthTex.createView(); + } + + override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0) { + flushPass(); + if( tex == null ) { + renderPassDesc = { + colorAttachments : [{ + view : colorView, + loadOp : Load, + storeOp: Store + }], + depthStencilAttachment: { + view : depthView, + depthLoadOp: Load, + depthStoreOp: Store, + stencilLoadOp: Load, + stencilStoreOp: Store, + }, + }; + return; + } + throw "TODO"; + } + + function beginFrame() { + if( device == null ) return; + colorTex = context.getCurrentTexture(); + colorView = colorTex.createView(); + commandList = device.createCommandEncoder(); + setRenderTarget(null); + } + + function flushPass() { + if( needClear ) { + renderPass = commandList.beginRenderPass(renderPassDesc); + for( c in renderPassDesc.colorAttachments ) + c.clearValue = js.Lib.undefined; + var depth = renderPassDesc.depthStencilAttachment; + if( depth != null ) { + depth.depthClearValue = js.Lib.undefined; + depth.stencilClearValue = js.Lib.undefined; + } + needClear = false; + } + if( renderPass != null ) { + renderPass.end(); + renderPass = null; + } + } + + function flushFrame() { + flushPass(); + if( commandList != null ) { + device.queue.submit([commandList.finish()]); + commandList = null; + } + } + + override function begin(frame:Int) { + beginFrame(); + } + + override function present() { + flushFrame(); + } + + override function clear(?color:h3d.Vector, ?depth:Float, ?stencil:Int) { + if( renderPass != null ) + flushPass(); + if( color != null ) { + for( c in renderPassDesc.colorAttachments ) { + c.clearValue = { r : color.r, g : color.g, b : color.b, a : color.a }; + c.loadOp = Clear; + } + } + var ds = renderPassDesc.depthStencilAttachment; + if( ds != null ) { + if( depth != null ) { + ds.depthClearValue = depth; + ds.depthLoadOp = Clear; + } + if( stencil != null ) { + ds.stencilClearValue = stencil; + ds.stencilLoadOp = Clear; + } + } + needClear = true; + } + + override function getDriverName(details:Bool) { + return "WebGPU"; + } + + override function allocVertexes(m:ManagedBuffer):VertexBuffer { + return cast {}; + } + + override function allocIndexes(count:Int, is32:Bool):IndexBuffer { + return cast {}; + } + + static var inst : WebGpuDriver; + static function checkReady() { + return true; + } + +} diff --git a/hxd/System.js.hx b/hxd/System.js.hx index 0130bb260e..298cb3fb1c 100644 --- a/hxd/System.js.hx +++ b/hxd/System.js.hx @@ -67,7 +67,14 @@ class System { throw "Cannot use browserLoop without Browser support nor defining nodejs + hxnodejs"; #end } + #if webgpu + if( @:privateAccess !h3d.impl.WebGpuDriver.checkReady() ) + return; + #end if( loopFunc != null ) loopFunc(); + #if webgpu + @:privateAccess h3d.impl.WebGpuDriver.inst.present(); + #end } public static function start( callb : Void -> Void ) : Void { From ee7c57f3ec1e2c9403dd7fa423cfdf3cad1a1974 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sat, 19 Aug 2023 08:21:38 +0200 Subject: [PATCH 02/15] wip --- h3d/impl/Driver.hx | 6 + h3d/impl/WebGpuApi.hx | 4 +- h3d/impl/WebGpuDriver.hx | 206 ++++++++++++-- hxsl/WgslOut.hx | 571 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 756 insertions(+), 31 deletions(-) create mode 100644 hxsl/WgslOut.hx diff --git a/h3d/impl/Driver.hx b/h3d/impl/Driver.hx index 2a1dc7fac5..83e41b7cd8 100644 --- a/h3d/impl/Driver.hx +++ b/h3d/impl/Driver.hx @@ -12,6 +12,12 @@ typedef VertexBuffer = Stage3dDriver.VertexWrapper; typedef Texture = flash.display3D.textures.TextureBase; typedef DepthBuffer = {}; typedef Query = {}; +#elseif (js && webgpu) +typedef IndexBuffer = { buf : WebGpuApi.GPUBuffer, stride : Int }; +typedef VertexBuffer = { buf : WebGpuApi.GPUBuffer, stride : Int }; +typedef Texture = {}; +typedef DepthBuffer = {}; +typedef Query = {}; #elseif js typedef IndexBuffer = { b : js.html.webgl.Buffer, is32 : Bool }; typedef VertexBuffer = { b : js.html.webgl.Buffer, stride : Int #if multidriver, driver : Driver #end }; diff --git a/h3d/impl/WebGpuApi.hx b/h3d/impl/WebGpuApi.hx index 55a271e635..f94f6405f2 100644 --- a/h3d/impl/WebGpuApi.hx +++ b/h3d/impl/WebGpuApi.hx @@ -917,8 +917,8 @@ enum abstract GPUIndexFormat(String) { } enum abstract GPUFrontFace(String) { - var Ccw = "ccw"; - var Cw = "cw"; + var CCW = "ccw"; + var CW = "cw"; } enum abstract GPUCullMode(String) { diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 73b0c84c2f..d27016d881 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -4,20 +4,38 @@ import h3d.impl.Driver; import h3d.impl.WebGpuApi; import h3d.mat.Pass; +class WebGpuShader { + public var inputs : InputNames; + public var pipeline : GPURenderPipeline; + public function new() { + } +} + +class WebGpuFrame { + public var colorTex : GPUTexture; + public var depthTex : GPUTexture; + public var colorView : GPUTextureView; + public var depthView : GPUTextureView; + public var toDelete : Array<{ function destroy() : Void; }> = []; + public function new() { + } +} + class WebGpuDriver extends h3d.impl.Driver { var canvas : js.html.CanvasElement; var context : GPUCanvasContext; var device : GPUDevice; - var colorTex : GPUTexture; - var depthTex : GPUTexture; - var colorView : GPUTextureView; - var depthView : GPUTextureView; var commandList : GPUCommandEncoder; var renderPass : GPURenderPassEncoder; var renderPassDesc : GPURenderPassDescriptor; var needClear : Bool; + var currentShader : WebGpuShader; + var shaderCache : Map = new Map(); + var frames : Array = []; + var frame : WebGpuFrame; + var frameCount : Int = 0; public function new() { inst = this; @@ -37,7 +55,6 @@ class WebGpuDriver extends h3d.impl.Driver { canvas = @:privateAccess hxd.Window.getInstance().canvas; context = cast canvas.getContext("webgpu"); resize(canvas.width, canvas.height); - beginFrame(); onCreate(false); }); }); @@ -58,15 +75,29 @@ class WebGpuDriver extends h3d.impl.Driver { alphaMode : Opaque, }); - colorTex = context.getCurrentTexture(); - colorView = colorTex.createView(); - depthTex = device.createTexture({ - size : { width : width, height : height }, - dimension : D2, - format : Depth24plus_stencil8, - usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC, - }); - depthView = depthTex.createView(); + if( frames != null ) { + for( f in frames ) { + f.depthTex.destroy(); + for( t in f.toDelete ) + t.destroy(); + } + } + + frames = []; + for( i in 0...2 ) { + var f = new WebGpuFrame(); + f.depthTex = device.createTexture({ + size : { width : width, height : height }, + dimension : D2, + format : Depth24plus_stencil8, + usage : (RENDER_ATTACHMENT:GPUTextureUsageFlags) | COPY_SRC, + }); + f.depthView = f.depthTex.createView(); + frames.push(f); + } + + frameCount = 0; + beginFrame(); } override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0) { @@ -74,12 +105,12 @@ class WebGpuDriver extends h3d.impl.Driver { if( tex == null ) { renderPassDesc = { colorAttachments : [{ - view : colorView, + view : frame.colorView, loadOp : Load, storeOp: Store }], depthStencilAttachment: { - view : depthView, + view : frame.depthView, depthLoadOp: Load, depthStoreOp: Store, stencilLoadOp: Load, @@ -93,23 +124,35 @@ class WebGpuDriver extends h3d.impl.Driver { function beginFrame() { if( device == null ) return; - colorTex = context.getCurrentTexture(); - colorView = colorTex.createView(); + frame = frames[(frameCount++)%frames.length]; + frame.colorTex = context.getCurrentTexture(); + frame.colorView = frame.colorTex.createView(); + for( t in frame.toDelete ) + t.destroy(); + frame.toDelete = []; commandList = device.createCommandEncoder(); setRenderTarget(null); + currentShader = null; + } + + function beginPass() { + if( renderPass != null ) + return; + renderPass = commandList.beginRenderPass(renderPassDesc); + for( c in renderPassDesc.colorAttachments ) + c.clearValue = js.Lib.undefined; + var depth = renderPassDesc.depthStencilAttachment; + if( depth != null ) { + depth.depthClearValue = js.Lib.undefined; + depth.stencilClearValue = js.Lib.undefined; + } + needClear = false; } function flushPass() { if( needClear ) { - renderPass = commandList.beginRenderPass(renderPassDesc); - for( c in renderPassDesc.colorAttachments ) - c.clearValue = js.Lib.undefined; - var depth = renderPassDesc.depthStencilAttachment; - if( depth != null ) { - depth.depthClearValue = js.Lib.undefined; - depth.stencilClearValue = js.Lib.undefined; - } - needClear = false; + if( renderPass != null ) throw "assert"; + beginPass(); } if( renderPass != null ) { renderPass.end(); @@ -123,6 +166,7 @@ class WebGpuDriver extends h3d.impl.Driver { device.queue.submit([commandList.finish()]); commandList = null; } + frame = frame == frames[0] ? frames[1] : frames[0]; } override function begin(frame:Int) { @@ -161,11 +205,115 @@ class WebGpuDriver extends h3d.impl.Driver { } override function allocVertexes(m:ManagedBuffer):VertexBuffer { - return cast {}; + return allocBuffer(VERTEX,m.size,m.stride << 2); } override function allocIndexes(count:Int, is32:Bool):IndexBuffer { - return cast {}; + return allocBuffer(INDEX,count,is32?2:4); + } + + function allocBuffer(type:GPUBufferUsage,count,stride) { + var buf = device.createBuffer({ + size : count * stride, + usage : (type:GPUBufferUsageFlags) | COPY_DST, + mappedAtCreation: false, + }); + return { buf : buf, count : count, stride : stride }; + } + + override function uploadVertexBytes(v:VertexBuffer, startVertex:Int, vertexCount:Int, buf:haxe.io.Bytes, bufPos:Int) { + uploadBuffer(v,startVertex,vertexCount,buf,bufPos); + } + + override function uploadIndexBytes(i:IndexBuffer, startIndice:Int, indiceCount:Int, buf:haxe.io.Bytes, bufPos:Int) { + uploadBuffer(i,startIndice,indiceCount,buf,bufPos); + } + + function uploadBuffer(b:VertexBuffer,start:Int,count:Int,buf:haxe.io.Bytes, bufPos:Int) { + var size = count * b.stride; + var tmpBuf = device.createBuffer({ + size : size, + usage : (MAP_WRITE:GPUBufferUsageFlags) | COPY_SRC, + mappedAtCreation : true, + }); + new js.lib.Uint8Array(tmpBuf.getMappedRange()).set(cast buf.getData(), bufPos); + tmpBuf.unmap(); + // copy + commandList.copyBufferToBuffer(tmpBuf,0,b.buf,start*b.stride,size); + // delete later + frame.toDelete.push(tmpBuf); + } + + + function compile( shader : hxsl.RuntimeShader.RuntimeShaderData ) { + var comp = new hxsl.WgslOut(); + var source = comp.run(shader.data); + trace(source); + return device.createShaderModule({ code : source }); + } + + function makeShader( shader : hxsl.RuntimeShader ) { + var sh = new WebGpuShader(); + var attribNames = []; + for( v in shader.vertex.data.vars ) { + if( v.kind != Input ) continue; + attribNames.push(v.name); + } + sh.inputs = InputNames.get(attribNames); + + var vertex = compile(shader.vertex); + var fragment = compile(shader.fragment); + + var layout = device.createPipelineLayout({ bindGroupLayouts: [] }); + var pipeline = device.createRenderPipeline({ + layout : layout, + vertex : { module : vertex, entryPoint : "main", buffers : [ + { + attributes: [{ shaderLocation : 0, offset : 0, format : Float32x3 }], + arrayStride: 4 * 3, + stepMode: GPUVertexStepMode.Vertex + }, + { + attributes: [{ shaderLocation : 1, offset : 0, format : Float32x3 }], + arrayStride: 4 * 3, // sizeof(float) * 3 + stepMode: GPUVertexStepMode.Vertex + } + ]}, + fragment : { module : fragment, entryPoint : "main", targets : [{ format : Bgra8unorm }] }, + primitive : { frontFace : CW, cullMode : None, topology : Triangle_list }, + depthStencil : { + depthWriteEnabled: true, + depthCompare: Less, + format: Depth24plus_stencil8 + } + }); + + sh.pipeline = pipeline; + + return sh; + } + + override function selectShader( shader : hxsl.RuntimeShader ) { + var sh = shaderCache.get(shader.id); + if( sh == null ) { + sh = makeShader(shader); + shaderCache.set(shader.id, sh); + } + if( sh == currentShader ) + return false; + currentShader = sh; + beginPass(); + renderPass.setPipeline(sh.pipeline); + return true; + } + + override function getShaderInputNames():InputNames { + return currentShader.inputs; + } + + override function draw(ibuf:IndexBuffer, startIndex:Int, ntriangles:Int) { + renderPass.setIndexBuffer(ibuf.buf, ibuf.stride==2?Uint16:Uint32, startIndex*ibuf.stride); + renderPass.draw(ntriangles*3); } static var inst : WebGpuDriver; diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx new file mode 100644 index 0000000000..b311bc3ab5 --- /dev/null +++ b/hxsl/WgslOut.hx @@ -0,0 +1,571 @@ +package hxsl; +using hxsl.Ast; + +class WgslOut { + + static var KWD_LIST = [ + ]; + static var KWDS = [for( k in KWD_LIST ) k => true]; + static var GLOBALS = { + var m = new Map(); + for( g in hxsl.Ast.TGlobal.createAll() ) { + var n = "" + g; + n = n.charAt(0).toLowerCase() + n.substr(1); + m.set(g, n); + } + for( g in m ) + KWDS.set(g, true); + m; + }; + + var buf : StringBuf; + var exprIds = 0; + var exprValues : Array; + var locals : Map; + var decls : Array; + var isVertex : Bool; + var allNames : Map; + public var varNames : Map; + + var varAccess : Map; + + public function new() { + varNames = new Map(); + allNames = new Map(); + } + + inline function add( v : Dynamic ) { + buf.add(v); + } + + inline function ident( v : TVar ) { + add(varName(v)); + } + + function decl( s : String ) { + for( d in decls ) + if( d == s ) return; + if( s.charCodeAt(0) == '#'.code ) + decls.unshift(s); + else + decls.push(s); + } + + function addType( t : Type ) { + switch( t ) { + case TVoid: + add("void"); + case TInt: + add("int"); + case TBytes(n): + add("uint"+n); + case TBool: + add("bool"); + case TFloat: + add("float"); + case TString: + add("string"); + case TVec(size, k): + switch( k ) { + case VFloat: add("float"); + case VInt: add("int"); + case VBool: add("bool"); + } + add(size); + case TMat2: + add("float2x2"); + case TMat3: + add("float3x3"); + case TMat4: + add("float4x4"); + case TMat3x4: + add("float4x3"); + case TSampler2D: + add("Texture2D"); + case TSamplerCube: + add("TextureCube"); + case TSampler2DArray: + add("Texture2DArray"); + case TStruct(vl): + add("struct { "); + for( v in vl ) { + addVar(v); + add(";"); + } + add(" }"); + case TFun(_): + add("function"); + case TArray(t, size), TBuffer(t,size): + addType(t); + add("["); + switch( size ) { + case SVar(v): + ident(v); + case SConst(v): + add(v); + } + add("]"); + case TChannel(n): + add("channel" + n); + } + } + + function addArraySize( size ) { + add("["); + switch( size ) { + case SVar(v): ident(v); + case SConst(n): add(n); + } + add("]"); + } + + function addVar( v : TVar ) { + switch( v.type ) { + case TArray(t, size), TBuffer(t,size): + addVar({ + id : v.id, + name : v.name, + type : t, + kind : v.kind, + }); + addArraySize(size); + default: + addType(v.type); + add(" "); + ident(v); + } + } + + function addValue( e : TExpr, tabs : String ) { + switch( e.e ) { + case TBlock(el): + var name = "val" + (exprIds++); + var tmp = buf; + buf = new StringBuf(); + addType(e.t); + add(" "); + add(name); + add("(void)"); + var el2 = el.copy(); + var last = el2[el2.length - 1]; + el2[el2.length - 1] = { e : TReturn(last), t : e.t, p : last.p }; + var e2 = { + t : TVoid, + e : TBlock(el2), + p : e.p, + }; + addExpr(e2, ""); + exprValues.push(buf.toString()); + buf = tmp; + add(name); + add("()"); + case TIf(econd, eif, eelse): + add("( "); + addValue(econd, tabs); + add(" ) ? "); + addValue(eif, tabs); + add(" : "); + addValue(eelse, tabs); + case TMeta(m,args,e): + handleMeta(m, args, addValue, e, tabs); + default: + addExpr(e, tabs); + } + } + + function handleMeta( m, args : Array, callb, e, tabs ) { + switch( [m, args] ) { + default: + callb(e,tabs); + } + } + + function addBlock( e : TExpr, tabs ) { + if( e.e.match(TBlock(_)) ) + addExpr(e,tabs); + else { + add("{"); + addExpr(e,tabs); + if( !isBlock(e) ) + add(";"); + add("}"); + } + } + + function addExpr( e : TExpr, tabs : String ) { + switch( e.e ) { + case TConst(c): + switch( c ) { + case CInt(v): add(v); + case CFloat(f): + var str = "" + f; + add(str); + if( str.indexOf(".") == -1 && str.indexOf("e") == -1 ) + add("."); + case CString(v): add('"' + v + '"'); + case CNull: add("null"); + case CBool(b): add(b); + } + case TVar(v): + var acc = varAccess.get(v.id); + if( acc != null ) add(acc); + ident(v); + case TGlobal(g): + add(GLOBALS.get(g)); + case TParenthesis(e): + add("("); + addValue(e,tabs); + add(")"); + case TBlock(el): + add("{\n"); + var t2 = tabs + "\t"; + for( e in el ) { + add(t2); + addExpr(e, t2); + newLine(e); + } + add(tabs); + add("}"); + case TVarDecl(v, { e : TArrayDecl(el) }): + locals.set(v.id, v); + for( i in 0...el.length ) { + ident(v); + add("["); + add(i); + add("] = "); + addExpr(el[i], tabs); + newLine(el[i]); + } + case TBinop(OpAssign,evar = { e : TVar(_) },{ e : TArrayDecl(el) }): + for( i in 0...el.length ) { + addExpr(evar, tabs); + add("["); + add(i); + add("] = "); + addExpr(el[i], tabs); + } + case TArrayDecl(el): + add("{"); + var first = true; + for( e in el ) { + if( first ) first = false else add(", "); + addValue(e,tabs); + } + add("}"); + case TBinop(op, e1, e2): + switch( [op, e1.t, e2.t] ) { + default: + addValue(e1, tabs); + add(" "); + add(Printer.opStr(op)); + add(" "); + addValue(e2, tabs); + } + case TUnop(op, e1): + add(switch(op) { + case OpNot: "!"; + case OpNeg: "-"; + case OpIncrement: "++"; + case OpDecrement: "--"; + case OpNegBits: "~"; + default: throw "assert"; // OpSpread for Haxe4.2+ + }); + addValue(e1, tabs); + case TVarDecl(v, init): + locals.set(v.id, v); + if( init != null ) { + ident(v); + add(" = "); + addValue(init, tabs); + } else { + add("/*var*/"); + } + case TCall(e, args): + addValue(e, tabs); + add("("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + addValue(e, tabs); + } + add(")"); + case TSwiz(e, regs): + addValue(e, tabs); + add("."); + for( r in regs ) + add(switch(r) { + case X: "x"; + case Y: "y"; + case Z: "z"; + case W: "w"; + }); + case TIf(econd, eif, eelse): + add("if( "); + addValue(econd, tabs); + add(") "); + addBlock(eif, tabs); + if( eelse != null ) { + add(" else "); + addBlock(eelse, tabs); + } + case TDiscard: + add("discard"); + case TReturn(e): + if( e == null ) + add("return _out"); + else { + add("return "); + addValue(e, tabs); + } + case TFor(v, it, loop): + locals.set(v.id, v); + switch( it.e ) { + case TBinop(OpInterval, e1, e2): + add("[loop] for("); + add(v.name+"="); + addValue(e1,tabs); + add(";"+v.name+"<"); + addValue(e2,tabs); + add(";" + v.name+"++) "); + addBlock(loop, tabs); + default: + throw "assert"; + } + case TWhile(e, loop, false): + var old = tabs; + tabs += "\t"; + add("[loop] do "); + addBlock(loop,tabs); + add(" while( "); + addValue(e,tabs); + add(" )"); + case TWhile(e, loop, _): + add("[loop] while( "); + addValue(e, tabs); + add(" ) "); + addBlock(loop,tabs); + case TSwitch(_): + add("switch(...)"); + case TContinue: + add("continue"); + case TBreak: + add("break"); + case TArray(e, index): + switch( e.t ) { + default: + addValue(e, tabs); + add("["); + addValue(index, tabs); + add("]"); + } + case TMeta(m, args, e): + handleMeta(m, args, addExpr, e, tabs); + } + } + + function varName( v : TVar ) { + var n = varNames.get(v.id); + if( n != null ) + return n; + n = v.name; + while( KWDS.exists(n) ) + n = "_" + n; + if( allNames.exists(n) ) { + var k = 2; + n += "_"; + while( allNames.exists(n + k) ) + k++; + n += k; + } + varNames.set(v.id, n); + allNames.set(n, v.id); + return n; + } + + function newLine( e : TExpr ) { + if( isBlock(e) ) + add("\n"); + else + add(";\n"); + } + + function isBlock( e : TExpr ) { + switch( e.e ) { + case TFor(_, _, loop), TWhile(_,loop,true): + return isBlock(loop); + case TIf(_,eif,eelse): + return isBlock(eelse == null ? eif : eelse); + case TBlock(_): + return true; + default: + return false; + } + } + + function collectGlobals( m : Map, e : TExpr ) { + switch( e.e ) { + case TGlobal(g): m.set(g,true); + default: e.iter(collectGlobals.bind(m)); + } + } + + function initVars( s : ShaderData ) { + var index = 0; + function declVar(prefix:String, v : TVar ) { + add("\t"); + addVar(v); + add(";\n"); + varAccess.set(v.id, prefix); + } + + var foundGlobals = new Map(); + for( f in s.funs ) + collectGlobals(foundGlobals, f.expr); + + add("struct s_input {\n"); + for( v in s.vars ) + if( v.kind == Input || (v.kind == Var && !isVertex) ) + declVar("_in.", v); + add("};\n\n"); + + add("struct s_output {\n"); + for( v in s.vars ) + if( v.kind == Output ) + declVar("_out.", v); + for( v in s.vars ) + if( v.kind == Var && isVertex ) + declVar("_out.", v); + add("};\n\n"); + } + + function initGlobals( s : ShaderData ) { + add('cbuffer _globals {\n'); + for( v in s.vars ) + if( v.kind == Global ) { + add("\t"); + addVar(v); + add(";\n"); + } + add("};\n\n"); + } + + function initParams( s : ShaderData ) { + var textures = []; + var buffers = []; + add('cbuffer _params {\n'); + for( v in s.vars ) + if( v.kind == Param ) { + switch( v.type ) { + case TArray(t, _) if( t.isSampler() ): + textures.push(v); + continue; + case TBuffer(_): + buffers.push(v); + continue; + default: + if( v.type.isSampler() ) { + textures.push(v); + continue; + } + } + add("\t"); + addVar(v); + add(";\n"); + } + add("};\n\n"); + + var bufCount = 0; + for( b in buffers ) { + add('cbuffer _buffer$bufCount { '); + addVar(b); + add("; };\n"); + bufCount++; + } + if( bufCount > 0 ) add("\n"); + + var texCount = 0; + for( v in textures ) { + addVar(v); + add(' : register(t${texCount});\n'); + switch( v.type ) { + case TArray(_,SConst(n)): texCount += n; + default: texCount++; + } + } + } + + function initStatics( s : ShaderData ) { + add("s_input _in;\n"); + add("s_output _out;\n"); + + add("\n"); + for( v in s.vars ) + if( v.kind == Local ) { + addVar(v); + add(";\n"); + } + add("\n"); + } + + function emitMain( expr : TExpr ) { + add("s_output main( s_input __in ) {\n"); + add("\t_in = __in;\n"); + switch( expr.e ) { + case TBlock(el): + for( e in el ) { + add("\t"); + addExpr(e, "\t"); + newLine(e); + } + default: + addExpr(expr, ""); + } + add("\treturn _out;\n"); + add("}"); + } + + function initLocals() { + var locals = Lambda.array(locals); + locals.sort(function(v1, v2) return Reflect.compare(v1.name, v2.name)); + for( v in locals ) { + addVar(v); + add(";\n"); + } + add("\n"); + + for( e in exprValues ) { + add(e); + add("\n\n"); + } + } + + public function run( s : ShaderData ) { + locals = new Map(); + decls = []; + buf = new StringBuf(); + exprValues = []; + + if( s.funs.length != 1 ) throw "assert"; + var f = s.funs[0]; + isVertex = f.kind == Vertex; + + varAccess = new Map(); + initVars(s); + initGlobals(s); + initParams(s); + initStatics(s); + + var tmp = buf; + buf = new StringBuf(); + emitMain(f.expr); + exprValues.push(buf.toString()); + buf = tmp; + + initLocals(); + + decls.push(buf.toString()); + buf = null; + return decls.join("\n"); + } + +} From e9ff7218564bd6406448c7aabb262cc472369f37 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sat, 4 Nov 2023 13:43:05 +0100 Subject: [PATCH 03/15] first pass at shader output --- hxsl/WgslOut.hx | 78 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index b311bc3ab5..54080a5208 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -3,8 +3,7 @@ using hxsl.Ast; class WgslOut { - static var KWD_LIST = [ - ]; + static var KWD_LIST = ["alias","const","const_assert","continuing","diagnostic","enable","fn","let","loop","requires","struct"]; static var KWDS = [for( k in KWD_LIST ) k => true]; static var GLOBALS = { var m = new Map(); @@ -56,22 +55,23 @@ class WgslOut { case TVoid: add("void"); case TInt: - add("int"); + add("i32"); case TBytes(n): - add("uint"+n); + add('vec$n'); case TBool: add("bool"); case TFloat: - add("float"); + add("f32"); case TString: add("string"); case TVec(size, k): + add('vec$size<'); switch( k ) { - case VFloat: add("float"); - case VInt: add("int"); + case VFloat: add("f32"); + case VInt: add("i32"); case VBool: add("bool"); } - add(size); + add('>'); case TMat2: add("float2x2"); case TMat3: @@ -94,7 +94,7 @@ class WgslOut { } add(" }"); case TFun(_): - add("function"); + add("fn "); case TArray(t, size), TBuffer(t,size): addType(t); add("["); @@ -130,9 +130,9 @@ class WgslOut { }); addArraySize(size); default: - addType(v.type); - add(" "); ident(v); + add(" : "); + addType(v.type); } } @@ -414,7 +414,7 @@ class WgslOut { function declVar(prefix:String, v : TVar ) { add("\t"); addVar(v); - add(";\n"); + add(",\n"); varAccess.set(v.id, prefix); } @@ -423,36 +423,60 @@ class WgslOut { collectGlobals(foundGlobals, f.expr); add("struct s_input {\n"); + var index = 0; for( v in s.vars ) - if( v.kind == Input || (v.kind == Var && !isVertex) ) + if( v.kind == Input || (v.kind == Var && !isVertex) ) { + add('@location(${index++}) '); declVar("_in.", v); + } add("};\n\n"); add("struct s_output {\n"); + var index = 0; for( v in s.vars ) - if( v.kind == Output ) - declVar("_out.", v); - for( v in s.vars ) - if( v.kind == Var && isVertex ) + if( v.kind == Output || (v.kind == Var && isVertex) ) { + if( v.kind == Output && isVertex ) + add('@builtin(position) '); + else + add('@location(${index++}) '); declVar("_out.", v); + } add("};\n\n"); } function initGlobals( s : ShaderData ) { - add('cbuffer _globals {\n'); + var found = false; + for( v in s.vars ) + if( v.kind == Global ) { + found = true; + break; + } + if( !found ) + return; + + add('struct _globals {\n'); for( v in s.vars ) if( v.kind == Global ) { add("\t"); addVar(v); - add(";\n"); + add(",\n"); } add("};\n\n"); } function initParams( s : ShaderData ) { + var found = false; + for( v in s.vars ) + if( v.kind == Param ) { + found = true; + break; + } + if( !found ) + return; + var textures = []; var buffers = []; - add('cbuffer _params {\n'); + add('struct _params {\n'); for( v in s.vars ) if( v.kind == Param ) { switch( v.type ) { @@ -495,21 +519,23 @@ class WgslOut { } function initStatics( s : ShaderData ) { - add("s_input _in;\n"); - add("s_output _out;\n"); + add("var _in : s_input;\n"); + add("var _out : s_output;\n"); add("\n"); for( v in s.vars ) if( v.kind == Local ) { + add("var "); addVar(v); add(";\n"); } add("\n"); } - function emitMain( expr : TExpr ) { - add("s_output main( s_input __in ) {\n"); - add("\t_in = __in;\n"); + function emitMain( s : ShaderData, expr : TExpr ) { + add(isVertex ? "@vertex " : "@fragment "); + add("fn main( in__ : s_input ) -> s_output {\n"); + add("\t_in = in__;\n"); switch( expr.e ) { case TBlock(el): for( e in el ) { @@ -557,7 +583,7 @@ class WgslOut { var tmp = buf; buf = new StringBuf(); - emitMain(f.expr); + emitMain(s, f.expr); exprValues.push(buf.toString()); buf = tmp; From 66eb794c290208d318121b2b74c2f12c85c6b701 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sat, 4 Nov 2023 19:42:40 +0100 Subject: [PATCH 04/15] simple triangle render ok --- h3d/impl/WebGpuApi.hx | 64 +++++++++++++------------- h3d/impl/WebGpuDriver.hx | 97 +++++++++++++++++++++++++++------------- hxsl/WgslOut.hx | 33 ++++++++++---- 3 files changed, 121 insertions(+), 73 deletions(-) diff --git a/h3d/impl/WebGpuApi.hx b/h3d/impl/WebGpuApi.hx index f94f6405f2..2e1f20cef4 100644 --- a/h3d/impl/WebGpuApi.hx +++ b/h3d/impl/WebGpuApi.hx @@ -287,40 +287,40 @@ enum GPUMapMode { typedef GPUMapModeFlags = haxe.EnumFlags; -extern class GPUBuffer { +extern class GPU_Buffer { var size(default,null) : GPUSize64; var usage(default,null) : GPUBufferUsageFlags; var mapState(default,null) : GPUBufferMapState; /** - * Maps the given range of the {@link GPUBuffer} and resolves the returned {@link Promise} when the - * {@link GPUBuffer}'s content is ready to be accessed with {@link GPUBuffer#getMappedRange}. + * Maps the given range of the {@link GPU_Buffer} and resolves the returned {@link Promise} when the + * {@link GPU_Buffer}'s content is ready to be accessed with {@link GPU_Buffer#getMappedRange}. * The resolution of the returned {@link Promise} **only** indicates that the buffer has been mapped. * It does not guarantee the completion of any other operations visible to the content timeline, * and in particular does not imply that any other {@link Promise} returned from - * {@link GPUQueue#onSubmittedWorkDone()} or {@link GPUBuffer#mapAsync} on other {@link GPUBuffer}s + * {@link GPUQueue#onSubmittedWorkDone()} or {@link GPU_Buffer#mapAsync} on other {@link GPU_Buffer}s * have resolved. * The resolution of the {@link Promise} returned from {@link GPUQueue#onSubmittedWorkDone} * **does** imply the completion of - * {@link GPUBuffer#mapAsync} calls made prior to that call, - * on {@link GPUBuffer}s last used exclusively on that queue. + * {@link GPU_Buffer#mapAsync} calls made prior to that call, + * on {@link GPU_Buffer}s last used exclusively on that queue. * @param mode - Whether the buffer should be mapped for reading or writing. * @param offset - Offset in bytes into the buffer to the start of the range to map. * @param size - Size in bytes of the range to map. */ function mapAsync( mode: GPUMapModeFlags, ?offset: GPUSize64, ?size: GPUSize64 ) : Promise; /** - * Returns an {@link ArrayBuffer} with the contents of the {@link GPUBuffer} in the given mapped range. + * Returns an {@link ArrayBuffer} with the contents of the {@link GPU_Buffer} in the given mapped range. * @param offset - Offset in bytes into the buffer to return buffer contents from. * @param size - Size in bytes of the {@link ArrayBuffer} to return. */ function getMappedRange( ?offset: GPUSize64, ?size: GPUSize64 ) : js.lib.ArrayBuffer; /** - * Unmaps the mapped range of the {@link GPUBuffer} and makes it's contents available for use by the + * Unmaps the mapped range of the {@link GPU_Buffer} and makes it's contents available for use by the * GPU again. */ function unmap() : Void; /** - * Destroys the {@link GPUBuffer}. + * Destroys the {@link GPU_Buffer}. * Note: It is valid to destroy a buffer multiple times. * Note: Since no further operations can be enqueued using this buffer, implementations can * free resource allocations, including mapped memory that was just unmapped. @@ -470,7 +470,7 @@ extern class GPUQueue { function submit( commandBuffers: Array ) : Void; function onSubmittedWorkDone() : Promise; /** - * Issues a write operation of the provided data into a {@link GPUBuffer}. + * Issues a write operation of the provided data into a {@link GPU_Buffer}. * @param buffer - The buffer to write to. * @param bufferOffset - Offset in bytes into `buffer` to begin writing at. * @param data - Data to write into `buffer`. @@ -479,7 +479,7 @@ extern class GPUQueue { * @param size - Size of content to write from `data` to `buffer`. Given in elements if * `data` is a `TypedArray` and bytes otherwise. */ - function writeBuffer( buffer: GPUBuffer, bufferOffset: GPUSize64, data: BufferSource, ?dataOffset: GPUSize64, ?size: GPUSize64 ) : Void; + function writeBuffer( buffer: GPU_Buffer, bufferOffset: GPUSize64, data: BufferSource, ?dataOffset: GPUSize64, ?size: GPUSize64 ) : Void; /** * Issues a write operation of the provided data into a {@link GPUTexture}. * @param destination - The texture subresource and origin to write to. @@ -619,7 +619,7 @@ typedef GPUBufferDescriptor = {> GPUObjectDescriptorBase, var usage: GPUBufferUsageFlags; /** * If `true` creates the buffer in an already mapped state, allowing - * {@link GPUBuffer#getMappedRange} to be called immediately. It is valid to set + * {@link GPU_Buffer#getMappedRange} to be called immediately. It is valid to set * {@link GPUBufferDescriptor#mappedAtCreation} to `true` even if {@link GPUBufferDescriptor#usage} * does not contain {@link GPUBufferUsage#MAP_READ} or {@link GPUBufferUsage#MAP_WRITE}. This can be * used to set the buffer's initial data. @@ -1153,7 +1153,7 @@ extern class GPURenderCommands extends GPUBindingCommands { * @param size - Size in bytes of the index data in `buffer`. * Defaults to the size of the buffer minus the offset. */ - function setIndexBuffer( buffer: GPUBuffer, indexFormat: GPUIndexFormat, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + function setIndexBuffer( buffer: GPU_Buffer, indexFormat: GPUIndexFormat, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; /** * Sets the current vertex buffer for the given slot. * @param slot - The vertex buffer slot to set the vertex buffer for. @@ -1162,7 +1162,7 @@ extern class GPURenderCommands extends GPUBindingCommands { * @param size - Size in bytes of the vertex data in `buffer`. * Defaults to the size of the buffer minus the offset. */ - function setVertexBuffer( slot: GPUIndex32, buffer: GPUBuffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + function setVertexBuffer( slot: GPUIndex32, buffer: GPU_Buffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; /** * Draws primitives. * See [[#rendering-operations]] for the detailed specification. @@ -1173,8 +1173,8 @@ extern class GPURenderCommands extends GPUBindingCommands { */ function draw( vertexCount: GPUSize32, ?instanceCount: GPUSize32, ?firstVertex: GPUSize32, ?firstInstance: GPUSize32 ) : Void; function drawIndexed( indexCount: GPUSize32, ?instanceCount: GPUSize32, ?firstIndex: GPUSize32, ?baseVertex: GPUSignedOffset32, ?firstInstance: GPUSize32 ) : Void; - function drawIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; - function drawIndexedIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; + function drawIndirect( indirectBuffer: GPU_Buffer, indirectOffset: GPUSize64 ) : Void; + function drawIndexedIndirect( indirectBuffer: GPU_Buffer, indirectOffset: GPUSize64 ) : Void; } typedef GPUColor = { @@ -1433,7 +1433,7 @@ extern class GPUComputePassEncoder extends GPUBindingCommands { function dispatchWorkgroups( workgroupCountX: GPUSize32, ?workgroupCountY: GPUSize32, ?workgroupCountZ: GPUSize32 ) : Void; /** * Dispatch work to be performed with the current {@link GPUComputePipeline} using parameters read - * from a {@link GPUBuffer}. + * from a {@link GPU_Buffer}. * See [[#computing-operations]] for the detailed specification. * packed block of **three 32-bit unsigned integer values (12 bytes total)**, * given in the same order as the arguments for {@link GPUComputePassEncoder#dispatchWorkgroups}. @@ -1441,7 +1441,7 @@ extern class GPUComputePassEncoder extends GPUBindingCommands { * @param indirectBuffer - Buffer containing the indirect dispatch parameters. * @param indirectOffset - Offset in bytes into `indirectBuffer` where the dispatch data begins. */ - function dispatchWorkgroupsIndirect( indirectBuffer: GPUBuffer, indirectOffset: GPUSize64 ) : Void; + function dispatchWorkgroupsIndirect( indirectBuffer: GPU_Buffer, indirectOffset: GPUSize64 ) : Void; /** * Completes recording of the compute pass commands sequence. */ @@ -1461,17 +1461,17 @@ extern class GPUCommandEncoder extends GPUDebugCommands { function beginComputePass( ?descriptor: GPUComputePassDescriptor ) : GPUComputePassEncoder; /** * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of a - * {@link GPUBuffer} to a sub-region of another {@link GPUBuffer}. - * @param source - The {@link GPUBuffer} to copy from. + * {@link GPU_Buffer} to a sub-region of another {@link GPU_Buffer}. + * @param source - The {@link GPU_Buffer} to copy from. * @param sourceOffset - Offset in bytes into `source` to begin copying from. - * @param destination - The {@link GPUBuffer} to copy to. + * @param destination - The {@link GPU_Buffer} to copy to. * @param destinationOffset - Offset in bytes into `destination` to place the copied data. * @param size - Bytes to copy. */ - function copyBufferToBuffer( source: GPUBuffer, sourceOffset: GPUSize64, destination: GPUBuffer, destinationOffset: GPUSize64, size: GPUSize64 ) : Void; + function copyBufferToBuffer( source: GPU_Buffer, sourceOffset: GPUSize64, destination: GPU_Buffer, destinationOffset: GPUSize64, size: GPUSize64 ) : Void; /** * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of a - * {@link GPUBuffer} to a sub-region of one or multiple continuous texture subresources. + * {@link GPU_Buffer} to a sub-region of one or multiple continuous texture subresources. * @param source - Combined with `copySize`, defines the region of the source buffer. * @param destination - Combined with `copySize`, defines the region of the destination texture subresource. * `copySize`: @@ -1479,7 +1479,7 @@ extern class GPUCommandEncoder extends GPUDebugCommands { function copyBufferToTexture( source: GPUImageCopyBuffer, destination: GPUImageCopyTexture, copySize: GPUExtent3D ) : Void; /** * Encode a command into the {@link GPUCommandEncoder} that copies data from a sub-region of one or - * multiple continuous texture subresourcesto a sub-region of a {@link GPUBuffer}. + * multiple continuous texture subresourcesto a sub-region of a {@link GPU_Buffer}. * @param source - Combined with `copySize`, defines the region of the source texture subresources. * @param destination - Combined with `copySize`, defines the region of the destination buffer. * `copySize`: @@ -1496,12 +1496,12 @@ extern class GPUCommandEncoder extends GPUDebugCommands { function copyTextureToTexture( source: GPUImageCopyTexture, destination: GPUImageCopyTexture, copySize: GPUExtent3D ) : Void; /** * Encode a command into the {@link GPUCommandEncoder} that fills a sub-region of a - * {@link GPUBuffer} with zeros. - * @param buffer - The {@link GPUBuffer} to clear. + * {@link GPU_Buffer} with zeros. + * @param buffer - The {@link GPU_Buffer} to clear. * @param offset - Offset in bytes into `buffer` where the sub-region to clear begins. * @param size - Size in bytes of the sub-region to clear. Defaults to the size of the buffer minus `offset`. */ - function clearBuffer( buffer: GPUBuffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; + function clearBuffer( buffer: GPU_Buffer, ?offset: GPUSize64, ?size: GPUSize64 ) : Void; /** * Writes a timestamp value into a querySet when all previous commands have completed executing. * @param querySet - The query set that will store the timestamp values. @@ -1509,14 +1509,14 @@ extern class GPUCommandEncoder extends GPUDebugCommands { */ function writeTimestamp( querySet: GPUQuerySet, queryIndex: GPUSize32 ) : Void; /** - * Resolves query results from a {@link GPUQuerySet} out into a range of a {@link GPUBuffer}. + * Resolves query results from a {@link GPUQuerySet} out into a range of a {@link GPU_Buffer}. * querySet: * firstQuery: * queryCount: * destination: * destinationOffset: */ - function resolveQuerySet( querySet: GPUQuerySet, firstQuery: GPUSize32, queryCount: GPUSize32, destination: GPUBuffer, destinationOffset: GPUSize64 ) : Void; + function resolveQuerySet( querySet: GPUQuerySet, firstQuery: GPUSize32, queryCount: GPUSize32, destination: GPU_Buffer, destinationOffset: GPUSize64 ) : Void; /** * Completes recording of the commands sequence and returns a corresponding {@link GPUCommandBuffer}. * descriptor: @@ -1614,9 +1614,9 @@ extern class GPUSampler {} typedef GPUBufferBinding = { /** - * The {@link GPUBuffer} to bind. + * The {@link GPU_Buffer} to bind. */ - var buffer: GPUBuffer; + var buffer: GPU_Buffer; /** * The offset, in bytes, from the beginning of {@link GPUBufferBinding#buffer} to the * beginning of the range exposed to the shader by the buffer binding. @@ -1840,7 +1840,7 @@ extern class GPUDevice { */ function destroy() : Void; - function createBuffer( descriptor: GPUBufferDescriptor ) : GPUBuffer; + function createBuffer( descriptor: GPUBufferDescriptor ) : GPU_Buffer; function createTexture( descriptor: GPUTextureDescriptor ) : GPUTexture; function createSampler( ?descriptor: GPUSamplerDescriptor ) : GPUSampler; //function importExternalTexture( descriptor: GPUExternalTextureDescriptor ) : GPUExternalTexture; diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index d27016d881..8709b9dd2d 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -5,7 +5,7 @@ import h3d.impl.WebGpuApi; import h3d.mat.Pass; class WebGpuShader { - public var inputs : InputNames; + public var format : hxd.BufferFormat; public var pipeline : GPURenderPipeline; public function new() { } @@ -28,11 +28,12 @@ class WebGpuDriver extends h3d.impl.Driver { var device : GPUDevice; var commandList : GPUCommandEncoder; + var commandUpload : GPUCommandEncoder; var renderPass : GPURenderPassEncoder; var renderPassDesc : GPURenderPassDescriptor; var needClear : Bool; var currentShader : WebGpuShader; - var shaderCache : Map = new Map(); + var shadersCache : Map = new Map(); var frames : Array = []; var frame : WebGpuFrame; var frameCount : Int = 0; @@ -100,7 +101,7 @@ class WebGpuDriver extends h3d.impl.Driver { beginFrame(); } - override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0) { + override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0, depthBinding : h3d.Engine.DepthBinding = ReadWrite ) { flushPass(); if( tex == null ) { renderPassDesc = { @@ -162,8 +163,12 @@ class WebGpuDriver extends h3d.impl.Driver { function flushFrame() { flushPass(); - if( commandList != null ) { - device.queue.submit([commandList.finish()]); + if( commandList != null || commandUpload != null ) { + var arr = []; + if( commandUpload != null ) arr.push(commandUpload.finish()); + if( commandList != null ) arr.push(commandList.finish()); + device.queue.submit(arr); + commandUpload = null; commandList = null; } frame = frame == frames[0] ? frames[1] : frames[0]; @@ -204,44 +209,70 @@ class WebGpuDriver extends h3d.impl.Driver { return "WebGPU"; } - override function allocVertexes(m:ManagedBuffer):VertexBuffer { - return allocBuffer(VERTEX,m.size,m.stride << 2); + override function allocBuffer( b : h3d.Buffer ):GPUBuffer { + return _allocBuffer(VERTEX,b.vertices,b.format.strideBytes); } override function allocIndexes(count:Int, is32:Bool):IndexBuffer { - return allocBuffer(INDEX,count,is32?2:4); + var stride = is32?4:2; + var buf = _allocBuffer(INDEX,count,stride); + return { buf : buf, stride : stride }; } - function allocBuffer(type:GPUBufferUsage,count,stride) { - var buf = device.createBuffer({ - size : count * stride, - usage : (type:GPUBufferUsageFlags) | COPY_DST, - mappedAtCreation: false, + function _allocBuffer(type:GPUBufferUsage,count,stride) { + var totSize = count * stride; + return device.createBuffer({ + size : (totSize + 3) & ~3, + usage : (type:GPUBufferUsageFlags), + mappedAtCreation: true, }); - return { buf : buf, count : count, stride : stride }; } - override function uploadVertexBytes(v:VertexBuffer, startVertex:Int, vertexCount:Int, buf:haxe.io.Bytes, bufPos:Int) { - uploadBuffer(v,startVertex,vertexCount,buf,bufPos); + override function uploadBufferData( b : Buffer, startVertex : Int, vertexCount : Int, buf : hxd.FloatBuffer, bufPos : Int ) { + var buf : js.lib.Float32Array = buf.getNative(); + var buf = new js.lib.Float32Array(buf.buffer, bufPos * 4, vertexCount * b.format.strideBytes >> 2); + uploadBuffer(b.vbuf,b.format.stride,startVertex,vertexCount,buf,0); + } + + override function uploadBufferBytes(v:h3d.Buffer, startVertex:Int, vertexCount:Int, buf:haxe.io.Bytes, bufPos:Int) { + uploadBuffer(v.vbuf,v.format.stride,startVertex,vertexCount,cast buf.getData(),bufPos); + } + + override function uploadIndexBuffer( i : IndexBuffer, startIndice : Int, indiceCount : Int, buf : hxd.IndexBuffer, bufPos : Int ) { + var buf = new js.lib.Uint16Array(buf.getNative()); + var sub = new js.lib.Uint16Array(buf.buffer, bufPos * i.stride, indiceCount); + uploadBuffer(i.buf,i.stride,startIndice,indiceCount,sub,0); } override function uploadIndexBytes(i:IndexBuffer, startIndice:Int, indiceCount:Int, buf:haxe.io.Bytes, bufPos:Int) { - uploadBuffer(i,startIndice,indiceCount,buf,bufPos); + uploadBuffer(i.buf,i.stride,startIndice,indiceCount,cast buf.getData(),bufPos); } - function uploadBuffer(b:VertexBuffer,start:Int,count:Int,buf:haxe.io.Bytes, bufPos:Int) { - var size = count * b.stride; + function uploadBuffer(buf:GPU_Buffer,stride:Int,start:Int,count:Int,data:Dynamic,bufPos:Int) { + /* + var size = ((count * stride) + 3) & ~3; var tmpBuf = device.createBuffer({ size : size, usage : (MAP_WRITE:GPUBufferUsageFlags) | COPY_SRC, mappedAtCreation : true, }); - new js.lib.Uint8Array(tmpBuf.getMappedRange()).set(cast buf.getData(), bufPos); + new js.lib.Uint8Array(tmpBuf.getMappedRange()).set(data, bufPos); tmpBuf.unmap(); // copy - commandList.copyBufferToBuffer(tmpBuf,0,b.buf,start*b.stride,size); + if( commandUpload == null ) + commandUpload = device.createCommandEncoder(); + commandUpload.copyBufferToBuffer(tmpBuf,0,buf,start*stride,size); // delete later frame.toDelete.push(tmpBuf); + */ + var map = buf.getMappedRange(); + if( data is js.lib.Uint16Array ) + new js.lib.Uint16Array(map).set(data,bufPos); + else if( data is js.lib.Float32Array ) + new js.lib.Float32Array(map).set(data,bufPos); + else + new js.lib.Uint8Array(map).set(data,bufPos); + buf.unmap(); } @@ -254,12 +285,13 @@ class WebGpuDriver extends h3d.impl.Driver { function makeShader( shader : hxsl.RuntimeShader ) { var sh = new WebGpuShader(); - var attribNames = []; + var format : Array = []; for( v in shader.vertex.data.vars ) { if( v.kind != Input ) continue; - attribNames.push(v.name); + var t = hxd.BufferFormat.InputFormat.fromHXSL(v.type); + format.push({ name : v.name, type : t }); } - sh.inputs = InputNames.get(attribNames); + sh.format = hxd.BufferFormat.make(format); var vertex = compile(shader.vertex); var fragment = compile(shader.fragment); @@ -270,12 +302,12 @@ class WebGpuDriver extends h3d.impl.Driver { vertex : { module : vertex, entryPoint : "main", buffers : [ { attributes: [{ shaderLocation : 0, offset : 0, format : Float32x3 }], - arrayStride: 4 * 3, + arrayStride: 4 * 6, stepMode: GPUVertexStepMode.Vertex }, { - attributes: [{ shaderLocation : 1, offset : 0, format : Float32x3 }], - arrayStride: 4 * 3, // sizeof(float) * 3 + attributes: [{ shaderLocation : 1, offset : 3*4, format : Float32x3 }], + arrayStride: 4 * 6, // sizeof(float) * 3 stepMode: GPUVertexStepMode.Vertex } ]}, @@ -294,10 +326,10 @@ class WebGpuDriver extends h3d.impl.Driver { } override function selectShader( shader : hxsl.RuntimeShader ) { - var sh = shaderCache.get(shader.id); + var sh = shadersCache.get(shader.id); if( sh == null ) { sh = makeShader(shader); - shaderCache.set(shader.id, sh); + shadersCache.set(shader.id, sh); } if( sh == currentShader ) return false; @@ -307,13 +339,14 @@ class WebGpuDriver extends h3d.impl.Driver { return true; } - override function getShaderInputNames():InputNames { - return currentShader.inputs; + override function selectBuffer(b:Buffer) { + for( i in 0...@:privateAccess currentShader.format.inputs.length ) + renderPass.setVertexBuffer(i, b.vbuf); } override function draw(ibuf:IndexBuffer, startIndex:Int, ntriangles:Int) { renderPass.setIndexBuffer(ibuf.buf, ibuf.stride==2?Uint16:Uint32, startIndex*ibuf.stride); - renderPass.draw(ntriangles*3); + renderPass.drawIndexed(ntriangles*3); } static var inst : WebGpuDriver; diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index 54080a5208..4da62c6191 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -24,6 +24,7 @@ class WgslOut { var decls : Array; var isVertex : Bool; var allNames : Map; + var hasVarying : Bool; public var varNames : Map; var varAccess : Map; @@ -422,14 +423,23 @@ class WgslOut { for( f in s.funs ) collectGlobals(foundGlobals, f.expr); - add("struct s_input {\n"); - var index = 0; + hasVarying = false; for( v in s.vars ) - if( v.kind == Input || (v.kind == Var && !isVertex) ) { - add('@location(${index++}) '); - declVar("_in.", v); + if( v.kind == Var ) { + hasVarying = true; + break; } - add("};\n\n"); + + if( isVertex || hasVarying ) { + add("struct s_input {\n"); + var index = 0; + for( v in s.vars ) + if( v.kind == Input || (v.kind == Var && !isVertex) ) { + add('@location(${index++}) '); + declVar("_in.", v); + } + add("};\n\n"); + } add("struct s_output {\n"); var index = 0; @@ -519,7 +529,8 @@ class WgslOut { } function initStatics( s : ShaderData ) { - add("var _in : s_input;\n"); + if( isVertex || hasVarying ) + add("var _in : s_input;\n"); add("var _out : s_output;\n"); add("\n"); @@ -534,8 +545,12 @@ class WgslOut { function emitMain( s : ShaderData, expr : TExpr ) { add(isVertex ? "@vertex " : "@fragment "); - add("fn main( in__ : s_input ) -> s_output {\n"); - add("\t_in = in__;\n"); + if( isVertex || hasVarying ) { + add("fn main( in__ : s_input ) -> s_output {\n"); + add("\t_in = in__;\n"); + } else { + add("fn main() -> s_output {\n"); + } switch( expr.e ) { case TBlock(el): for( e in el ) { From f296fcbab9629bd29060d38a1fd5da23c2e9b57f Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 5 Nov 2023 21:23:58 +0100 Subject: [PATCH 05/15] basic params uniform buffer --- h3d/impl/WebGpuApi.hx | 3 +- h3d/impl/WebGpuDriver.hx | 105 ++++++++++++++++++++++++++++++++++----- hxsl/WgslOut.hx | 34 ++++--------- 3 files changed, 105 insertions(+), 37 deletions(-) diff --git a/h3d/impl/WebGpuApi.hx b/h3d/impl/WebGpuApi.hx index 2e1f20cef4..1372996bc1 100644 --- a/h3d/impl/WebGpuApi.hx +++ b/h3d/impl/WebGpuApi.hx @@ -1631,7 +1631,8 @@ typedef GPUBufferBinding = { extern class GPUExternalTexture {} -typedef GPUBindingResource = haxe.ds.Either>>; +abstract GPUBindingResource(Dynamic) from GPUSampler from GPUTextureView from GPUBufferBinding from GPUExternalTexture { +} typedef GPUBindGroupEntry = { diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 8709b9dd2d..cfeb63cf2c 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -4,8 +4,20 @@ import h3d.impl.Driver; import h3d.impl.WebGpuApi; import h3d.mat.Pass; +class WebGpuSubShader { + public var kind : GPUShaderStage; + public var module : GPUShaderModule; + public var groups : Array; + public var paramsBufferSize : Int; + public function new() { + } +} + class WebGpuShader { public var format : hxd.BufferFormat; + public var vertex : WebGpuSubShader; + public var fragment : WebGpuSubShader; + public var layout : GPUPipelineLayout; public var pipeline : GPURenderPipeline; public function new() { } @@ -276,30 +288,53 @@ class WebGpuDriver extends h3d.impl.Driver { } - function compile( shader : hxsl.RuntimeShader.RuntimeShaderData ) { + function compile( shader : hxsl.RuntimeShader.RuntimeShaderData, kind ) { var comp = new hxsl.WgslOut(); var source = comp.run(shader.data); - trace(source); - return device.createShaderModule({ code : source }); + var sh = new WebGpuSubShader(); + sh.kind = kind; + sh.module = device.createShaderModule({ code : source }); + sh.groups = []; + for( v in shader.data.vars ) { + switch( v.kind ) { + case Param: + var size = hxsl.Ast.Tools.size(v.type) * 4; + var g = device.createBindGroupLayout({ entries : [{ + binding: 0, + visibility : kind, + buffer : { + type : Uniform, + hasDynamicOffset : false, + minBindingSize: size, + } + }]}); + sh.paramsBufferSize = size; + sh.groups.push(g); + default: + } + } + return sh; } function makeShader( shader : hxsl.RuntimeShader ) { var sh = new WebGpuShader(); var format : Array = []; for( v in shader.vertex.data.vars ) { - if( v.kind != Input ) continue; - var t = hxd.BufferFormat.InputFormat.fromHXSL(v.type); - format.push({ name : v.name, type : t }); + switch( v.kind ) { + case Input: + var t = hxd.BufferFormat.InputFormat.fromHXSL(v.type); + format.push({ name : v.name, type : t }); + default: + } } sh.format = hxd.BufferFormat.make(format); + sh.vertex = compile(shader.vertex,VERTEX); + sh.fragment = compile(shader.fragment,FRAGMENT); + sh.layout = device.createPipelineLayout({ bindGroupLayouts: sh.vertex.groups.concat(sh.fragment.groups) }); - var vertex = compile(shader.vertex); - var fragment = compile(shader.fragment); - - var layout = device.createPipelineLayout({ bindGroupLayouts: [] }); var pipeline = device.createRenderPipeline({ - layout : layout, - vertex : { module : vertex, entryPoint : "main", buffers : [ + layout : sh.layout, + vertex : { module : sh.vertex.module, entryPoint : "main", buffers : [ { attributes: [{ shaderLocation : 0, offset : 0, format : Float32x3 }], arrayStride: 4 * 6, @@ -311,7 +346,7 @@ class WebGpuDriver extends h3d.impl.Driver { stepMode: GPUVertexStepMode.Vertex } ]}, - fragment : { module : fragment, entryPoint : "main", targets : [{ format : Bgra8unorm }] }, + fragment : { module : sh.fragment.module, entryPoint : "main", targets : [{ format : Bgra8unorm }] }, primitive : { frontFace : CW, cullMode : None, topology : Triangle_list }, depthStencil : { depthWriteEnabled: true, @@ -344,6 +379,50 @@ class WebGpuDriver extends h3d.impl.Driver { renderPass.setVertexBuffer(i, b.vbuf); } + override function uploadShaderBuffers(buffers:h3d.shader.Buffers, which:h3d.shader.Buffers.BufferKind) { + _uploadShaderBuffers(buffers.vertex, which, currentShader.vertex); + _uploadShaderBuffers(buffers.fragment, which, currentShader.fragment); + } + + function _uploadShaderBuffers(buffers:h3d.shader.Buffers.ShaderBuffers, which:h3d.shader.Buffers.BufferKind, sh:WebGpuSubShader) { + switch( which ) { + case Globals: + if( buffers.globals.length == 0 ) + return; + throw "TODO"; + case Params: + if( buffers.params.length == 0 ) + return; + var flags = new haxe.EnumFlags(); + var buffer = device.createBuffer({ + size : sh.paramsBufferSize, + usage : UNIFORM, + mappedAtCreation : true, + }); + var map = buffer.getMappedRange(); + new js.lib.Float32Array(map).set(cast buffers.params); + buffer.unmap(); + var group = device.createBindGroup({ + layout : sh.groups[0], + entries: [{ + binding: 0, + resource: { + buffer : buffer, + } + }], + }); + renderPass.setBindGroup(0, group); + case Textures: + if( buffers.tex.length == 0 ) + return; + throw "TODO"; + case Buffers: + if( buffers.buffers == null || buffers.buffers.length == 0 ) + return; + throw "TODO"; + } + } + override function draw(ibuf:IndexBuffer, startIndex:Int, ntriangles:Int) { renderPass.setIndexBuffer(ibuf.buf, ibuf.stride==2?Uint16:Uint32, startIndex*ibuf.stride); renderPass.drawIndexed(ntriangles*3); diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index 4da62c6191..11a662e836 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -74,13 +74,13 @@ class WgslOut { } add('>'); case TMat2: - add("float2x2"); + add("mat2x2f"); case TMat3: - add("float3x3"); + add("mat3x3f"); case TMat4: - add("float4x4"); + add("mat4x4f"); case TMat3x4: - add("float4x3"); + add("mat3x4f"); case TSampler2D: add("Texture2D"); case TSamplerCube: @@ -97,15 +97,16 @@ class WgslOut { case TFun(_): add("fn "); case TArray(t, size), TBuffer(t,size): + add("array<"); addType(t); - add("["); + add(","); switch( size ) { case SVar(v): ident(v); case SConst(v): add(v); } - add("]"); + add(">"); case TChannel(n): add("channel" + n); } @@ -121,20 +122,9 @@ class WgslOut { } function addVar( v : TVar ) { - switch( v.type ) { - case TArray(t, size), TBuffer(t,size): - addVar({ - id : v.id, - name : v.name, - type : t, - kind : v.kind, - }); - addArraySize(size); - default: - ident(v); - add(" : "); - addType(v.type); - } + ident(v); + add(" : "); + addType(v.type); } function addValue( e : TExpr, tabs : String ) { @@ -486,7 +476,6 @@ class WgslOut { var textures = []; var buffers = []; - add('struct _params {\n'); for( v in s.vars ) if( v.kind == Param ) { switch( v.type ) { @@ -502,11 +491,10 @@ class WgslOut { continue; } } - add("\t"); + add("@group(0) @binding(0) var "); addVar(v); add(";\n"); } - add("};\n\n"); var bufCount = 0; for( b in buffers ) { From ed16bca5fb965b32bac280352569c5361593acf6 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 12 Nov 2023 14:58:17 +0100 Subject: [PATCH 06/15] separate pipeline cache from driver --- h3d/impl/DX12Driver.hx | 202 +++++++++----------------------------- h3d/impl/PipelineCache.hx | 161 ++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 h3d/impl/PipelineCache.hx diff --git a/h3d/impl/DX12Driver.hx b/h3d/impl/DX12Driver.hx index 1ad440ea76..59382d72f0 100644 --- a/h3d/impl/DX12Driver.hx +++ b/h3d/impl/DX12Driver.hx @@ -81,14 +81,6 @@ class DxFrame { } } -class CachedPipeline { - public var bytes : hl.Bytes; - public var size : Int; - public var pipeline : GraphicsPipelineState; - public function new() { - } -} - class ShaderRegisters { public var globals : Int; public var params : Int; @@ -106,7 +98,7 @@ class CompiledShader { public var fragmentRegisters : ShaderRegisters; public var format : hxd.BufferFormat; public var pipeline : GraphicsPipelineStateDesc; - public var pipelines : Map> = new Map(); + public var pipelines : PipelineCache = new PipelineCache(); public var rootSignature : RootSignature; public var inputLayout : hl.CArray; public var inputCount : Int; @@ -291,16 +283,7 @@ class QueryData { class DX12Driver extends h3d.impl.Driver { - static inline var PSIGN_MATID = 0; - static inline var PSIGN_COLOR_MASK = PSIGN_MATID + 4; - static inline var PSIGN_UNUSED = PSIGN_COLOR_MASK + 1; - static inline var PSIGN_STENCIL_MASK = PSIGN_UNUSED + 1; - static inline var PSIGN_STENCIL_OPS = PSIGN_STENCIL_MASK + 2; - static inline var PSIGN_RENDER_TARGETS = PSIGN_STENCIL_OPS + 4; - static inline var PSIGN_LAYOUT = PSIGN_RENDER_TARGETS + 8; - - var pipelineSignature = new hl.Bytes(64); - var adlerOut = new hl.Bytes(4); + var pipelineBuilder = new PipelineCache.PipelineBuilder(); var driver : DriverInstance; var hasDeviceError = false; @@ -317,7 +300,6 @@ class DX12Driver extends h3d.impl.Driver { var currentFrame : Int; var fenceValue : Int64 = 0; - var needPipelineFlush = false; var currentPass : h3d.mat.Pass; var currentWidth : Int; @@ -584,28 +566,6 @@ class DX12Driver extends h3d.impl.Driver { res.state = to; } - - function getRTBits( tex : h3d.mat.Texture ) { - inline function mk(channels,format) { - return ((channels - 1) << 2) | (format + 1); - } - return switch( tex.format ) { - case RGBA: mk(4,0); - case R8: mk(1, 0); - case RG8: mk(2, 0); - case RGB8: mk(3, 0); - case R16F: mk(1,1); - case RG16F: mk(2,1); - case RGB16F: mk(3,1); - case RGBA16F: mk(4,1); - case R32F: mk(1,2); - case RG32F: mk(2,2); - case RGB32F: mk(3,2); - case RGBA32F: mk(4,2); - default: throw "Unsupported RT format "+tex.format; - } - } - function getDepthViewFromTexture( tex : h3d.mat.Texture, readOnly : Bool ) { if ( tex != null && tex.depthBuffer == null ) { depthEnabled = false; @@ -708,8 +668,7 @@ class DX12Driver extends h3d.impl.Driver { if( w == 0 ) w = 1; if( h == 0 ) h = 1; initViewport(w, h); - pipelineSignature.setI32(PSIGN_RENDER_TARGETS, tex == null ? 0 : getRTBits(tex) | (depthEnabled ? 0x80000000 : 0)); - needPipelineFlush = true; + pipelineBuilder.setRenderTarget(tex,depthEnabled); } override function setRenderTargets(textures:Array, depthBinding : h3d.Engine.DepthBinding = ReadWrite) { @@ -720,7 +679,6 @@ class DX12Driver extends h3d.impl.Driver { var t0 = textures[0]; var texViews = renderTargetViews.alloc(textures.length); - var bits = 0; for( i => t in textures ) { if ( t.t == null ) { t.alloc(); @@ -730,7 +688,6 @@ class DX12Driver extends h3d.impl.Driver { Driver.createRenderTargetView(t.t.res, null, view); tmp.renderTargets[i] = view; currentRenderTargets[i] = t; - bits |= getRTBits(t) << (i << 2); if ( !t.flags.has(WasCleared) ) { t.flags.set(WasCleared); var clear = tmp.clearColor; @@ -745,9 +702,7 @@ class DX12Driver extends h3d.impl.Driver { frame.commandList.omSetRenderTargets(textures.length, tmp.renderTargets, true, depthEnabled ? getDepthViewFromTexture(t0, depthBinding == ReadOnly) : null); initViewport(t0.width, t0.height); - - pipelineSignature.setI32(PSIGN_RENDER_TARGETS, bits | (depthEnabled ? 0x80000000 : 0)); - needPipelineFlush = true; + pipelineBuilder.setRenderTargets(textures, depthEnabled); } override function setDepth(depthBuffer : h3d.mat.Texture) { @@ -758,9 +713,7 @@ class DX12Driver extends h3d.impl.Driver { while( currentRenderTargets.length > 0 ) currentRenderTargets.pop(); initViewport(depthBuffer.width, depthBuffer.height); - - pipelineSignature.setI32(PSIGN_RENDER_TARGETS, 0x80000000); - needPipelineFlush = true; + pipelineBuilder.setDepth(depthBuffer); } override function setRenderZone(x:Int, y:Int, width:Int, height:Int) { @@ -1656,26 +1609,17 @@ class DX12Driver extends h3d.impl.Driver { if( currentShader == sh ) return false; currentShader = sh; - needPipelineFlush = true; + pipelineBuilder.setShader(shader); frame.commandList.setGraphicsRootSignature(currentShader.rootSignature); return true; } - override function selectMaterial( pass : h3d.mat.Pass ) @:privateAccess { - needPipelineFlush = true; - pipelineSignature.setI32(PSIGN_MATID, pass.bits); - pipelineSignature.setUI8(PSIGN_COLOR_MASK, pass.colorMask); + override function selectMaterial( pass : h3d.mat.Pass ) { + pipelineBuilder.selectMaterial(pass); var st = pass.stencil; - if( st != null ) { - pipelineSignature.setUI16(PSIGN_STENCIL_MASK, st.maskBits & 0xFFFF); - pipelineSignature.setI32(PSIGN_STENCIL_OPS, st.opBits); - if( curStencilRef != st.reference ) { - curStencilRef = st.reference; - frame.commandList.omSetStencilRef(st.reference); - } - } else { - pipelineSignature.setUI16(PSIGN_STENCIL_MASK, 0); - pipelineSignature.setI32(PSIGN_STENCIL_OPS, 0); + if( st != null && curStencilRef != st.reference ) { + curStencilRef = st.reference; + frame.commandList.omSetStencilRef(st.reference); } } @@ -1691,9 +1635,8 @@ class DX12Driver extends h3d.impl.Driver { v.sizeInBytes = bview.sizeInBytes; v.strideInBytes = bview.strideInBytes; if( inf.offset >= 256 ) throw "assert"; - pipelineSignature.setUI8(PSIGN_LAYOUT + i, inf.offset | inf.precision.toInt()); + pipelineBuilder.setBuffer(i, inf); } - needPipelineFlush = true; frame.commandList.iaSetVertexBuffers(0, currentShader.inputCount, views[0]); } @@ -1708,9 +1651,8 @@ class DX12Driver extends h3d.impl.Driver { v.sizeInBytes = bview.sizeInBytes; v.strideInBytes = bview.strideInBytes; if( inf.offset >= 256 ) throw "assert"; - pipelineSignature.setUI8(PSIGN_LAYOUT + i, inf.offset | inf.precision.toInt()); + pipelineBuilder.setBuffer(i, inf); } - needPipelineFlush = true; frame.commandList.iaSetVertexBuffers(0, map.length, views[0]); } @@ -1729,44 +1671,30 @@ class DX12Driver extends h3d.impl.Driver { function makePipeline( shader : CompiledShader ) { var p = shader.pipeline; - var passBits = pipelineSignature.getI32(PSIGN_MATID); - var colorMask = pipelineSignature.getUI8(PSIGN_COLOR_MASK); - var stencilMask = pipelineSignature.getUI16(PSIGN_STENCIL_MASK); - var stencilOp = pipelineSignature.getI32(PSIGN_STENCIL_OPS); - - var csrc = Pass.getBlendSrc(passBits); - var cdst = Pass.getBlendDst(passBits); - var asrc = Pass.getBlendAlphaSrc(passBits); - var adst = Pass.getBlendAlphaDst(passBits); - var cop = Pass.getBlendOp(passBits); - var aop = Pass.getBlendAlphaOp(passBits); - var dw = Pass.getDepthWrite(passBits); - var cmp = Pass.getDepthTest(passBits); - var cull = Pass.getCulling(passBits); - var wire = Pass.getWireframe(passBits); - if( wire != 0 ) cull = 0; + var pass = pipelineBuilder.getCurrentPass(); + if( pass.wireframe ) pass.culling = None; var rtCount = currentRenderTargets.length; if( rtCount == 0 ) rtCount = 1; p.numRenderTargets = rtCount; - p.rasterizerState.cullMode = CULL[cull]; - p.rasterizerState.fillMode = wire == 0 ? SOLID : WIREFRAME; - p.depthStencilDesc.depthEnable = cmp != 0; - p.depthStencilDesc.depthWriteMask = dw == 0 || !depthEnabled ? ZERO : ALL; - p.depthStencilDesc.depthFunc = COMP[cmp]; + p.rasterizerState.cullMode = CULL[pass.culling.getIndex()]; + p.rasterizerState.fillMode = pass.wireframe ? WIREFRAME : SOLID; + p.depthStencilDesc.depthEnable = pass.depthTest != Always; + p.depthStencilDesc.depthWriteMask = !pass.depthWrite || !depthEnabled ? ZERO : ALL; + p.depthStencilDesc.depthFunc = COMP[pass.depthTest.getIndex()]; var bl = p.blendState; for( i in 0...rtCount ) { var t = bl.renderTargets[i]; - t.blendEnable = csrc != 0 || cdst != 1; - t.srcBlend = BLEND[csrc]; - t.dstBlend = BLEND[cdst]; - t.srcBlendAlpha = BLEND_ALPHA[asrc]; - t.dstBlendAlpha = BLEND_ALPHA[adst]; - t.blendOp = BLEND_OP[cop]; - t.blendOpAlpha = BLEND_OP[aop]; - t.renderTargetWriteMask = colorMask; + t.blendEnable = pass.blendSrc != One || pass.blendDst != Zero; + t.srcBlend = BLEND[pass.blendSrc.getIndex()]; + t.dstBlend = BLEND[pass.blendDst.getIndex()]; + t.srcBlendAlpha = BLEND_ALPHA[pass.blendAlphaSrc.getIndex()]; + t.dstBlendAlpha = BLEND_ALPHA[pass.blendAlphaDst.getIndex()]; + t.blendOp = BLEND_OP[pass.blendOp.getIndex()]; + t.blendOpAlpha = BLEND_OP[pass.blendAlphaOp.getIndex()]; + t.renderTargetWriteMask = pass.colorMask; var t = currentRenderTargets[i]; p.rtvFormats[i] = t == null ? R8G8B8A8_UNORM : t.t.format; @@ -1775,9 +1703,9 @@ class DX12Driver extends h3d.impl.Driver { for( i in 0...shader.inputCount ) { var d = shader.inputLayout[i]; - var offset = pipelineSignature.getUI8(PSIGN_LAYOUT + i); - d.alignedByteOffset = offset & ~3; - d.format = @:privateAccess switch( [shader.format.inputs[i].type, new hxd.BufferFormat.Precision(offset&3)] ) { + var inf = pipelineBuilder.getBufferInput(i); + d.alignedByteOffset = inf.offset; + d.format = @:privateAccess switch( [shader.format.inputs[i].type, inf.precision] ) { case [DFloat, F32]: R32_FLOAT; case [DFloat, F16]: R16_FLOAT; case [DFloat, S8]: R8_SNORM; @@ -1799,65 +1727,33 @@ class DX12Driver extends h3d.impl.Driver { }; } - var stencil = stencilMask != 0 || stencilOp != 0; + var stencil = pass.stencil; var st = p.depthStencilDesc; - st.stencilEnable = stencil; - if( stencil ) { + st.stencilEnable = stencil != null; + if( stencil != null ) { var front = st.frontFace; var back = st.backFace; - st.stencilReadMask = stencilMask & 0xFF; - st.stencilWriteMask = stencilMask >> 8; - front.stencilFunc = COMP[Stencil.getFrontTest(stencilOp)]; - front.stencilPassOp = STENCIL_OP[Stencil.getFrontPass(stencilOp)]; - front.stencilFailOp = STENCIL_OP[Stencil.getFrontSTfail(stencilOp)]; - front.stencilDepthFailOp = STENCIL_OP[Stencil.getFrontDPfail(stencilOp)]; - back.stencilFunc = COMP[Stencil.getBackTest(stencilOp)]; - back.stencilPassOp = STENCIL_OP[Stencil.getBackPass(stencilOp)]; - back.stencilFailOp = STENCIL_OP[Stencil.getBackSTfail(stencilOp)]; - back.stencilDepthFailOp = STENCIL_OP[Stencil.getBackDPfail(stencilOp)]; + st.stencilReadMask = stencil.readMask; + st.stencilWriteMask = stencil.writeMask; + front.stencilFunc = COMP[stencil.frontTest.getIndex()]; + front.stencilPassOp = STENCIL_OP[stencil.frontPass.getIndex()]; + front.stencilFailOp = STENCIL_OP[stencil.frontSTfail.getIndex()]; + front.stencilDepthFailOp = STENCIL_OP[stencil.frontDPfail.getIndex()]; + back.stencilFunc = COMP[stencil.backTest.getIndex()]; + back.stencilPassOp = STENCIL_OP[stencil.backPass.getIndex()]; + back.stencilFailOp = STENCIL_OP[stencil.backSTfail.getIndex()]; + back.stencilDepthFailOp = STENCIL_OP[stencil.backDPfail.getIndex()]; } return Driver.createGraphicsPipelineState(p); } function flushPipeline() { - if( !needPipelineFlush ) return; - needPipelineFlush = false; - var signature = pipelineSignature; - var signatureSize = PSIGN_LAYOUT + currentShader.inputCount; - adlerOut.setI32(0, 0); - hl.Format.digest(adlerOut, signature, signatureSize, 3); - var hash = adlerOut.getI32(0); - var pipes = currentShader.pipelines.get(hash); - if( pipes == null ) { - pipes = new hl.NativeArray(1); - currentShader.pipelines.set(hash, pipes); - } - var insert = -1; - for( i in 0...pipes.length ) { - var p = pipes[i]; - if( p == null ) { - insert = i; - break; - } - if( p.size == signatureSize && p.bytes.compare(0, signature, 0, signatureSize) == 0 ) { - frame.commandList.setPipelineState(p.pipeline); - return; - } - } - var signatureBytes = @:privateAccess new haxe.io.Bytes(pipelineSignature, signatureSize); - if( insert < 0 ) { - var pipes2 = new hl.NativeArray(pipes.length + 1); - pipes2.blit(0, pipes, 0, insert); - currentShader.pipelines.set(hash, pipes2); - pipes = pipes2; - } - var cp = new CachedPipeline(); - cp.bytes = signature.sub(0, signatureSize); - cp.size = signatureSize; - cp.pipeline = makePipeline(currentShader); - pipes[insert] = cp; - frame.commandList.setPipelineState(cp.pipeline); + if( !pipelineBuilder.needFlush ) return; + var cache = pipelineBuilder.lookup(currentShader.pipelines, currentShader.inputCount); + if( cache.pipeline == null ) + cache.pipeline = makePipeline(currentShader); + frame.commandList.setPipelineState(cache.pipeline); } // QUERIES diff --git a/h3d/impl/PipelineCache.hx b/h3d/impl/PipelineCache.hx new file mode 100644 index 0000000000..62af07be67 --- /dev/null +++ b/h3d/impl/PipelineCache.hx @@ -0,0 +1,161 @@ +package h3d.impl; + +@:generic class CachedPipeline { + public var bytes : hl.Bytes; + public var size : Int; + public var pipeline : T; + public function new() { + } +} + +@:forward(get,set) +abstract PipelineCache(Map>>) { + + public function new() { + this = new Map(); + } + +} + +class PipelineBuilder { + + static inline var PSIGN_MATID = 0; + static inline var PSIGN_COLOR_MASK = PSIGN_MATID + 4; + static inline var PSIGN_UNUSED = PSIGN_COLOR_MASK + 1; + static inline var PSIGN_STENCIL_MASK = PSIGN_UNUSED + 1; + static inline var PSIGN_STENCIL_OPS = PSIGN_STENCIL_MASK + 2; + static inline var PSIGN_RENDER_TARGETS = PSIGN_STENCIL_OPS + 4; + static inline var PSIGN_LAYOUT = PSIGN_RENDER_TARGETS + 8; + static inline var MAX_BUFFERS = 8; + static inline var PSIGN_SIZE = PSIGN_LAYOUT + MAX_BUFFERS * 2; + + public var needFlush : Bool; + var signature = new hl.Bytes(64); + var adlerOut = new hl.Bytes(4); + var tmpPass = new h3d.mat.Pass(""); + var tmpStencil = new h3d.mat.Stencil(); + + public function new() { + } + + static function getRTBits( tex : h3d.mat.Texture ) { + inline function mk(channels,format) { + return ((channels - 1) << 2) | (format + 1); + } + return switch( tex.format ) { + case RGBA: mk(4,0); + case R8: mk(1, 0); + case RG8: mk(2, 0); + case RGB8: mk(3, 0); + case R16F: mk(1,1); + case RG16F: mk(2,1); + case RGB16F: mk(3,1); + case RGBA16F: mk(4,1); + case R32F: mk(1,2); + case RG32F: mk(2,2); + case RGB32F: mk(3,2); + case RGBA32F: mk(4,2); + default: throw "Unsupported RT format "+tex.format; + } + } + + public inline function setShader( sh : hxsl.RuntimeShader ) { + needFlush = true; + } + + public function setRenderTarget( tex : h3d.mat.Texture, depthEnabled : Bool ) { + signature.setI32(PSIGN_RENDER_TARGETS, tex == null ? 0 : getRTBits(tex) | (depthEnabled ? 0x80000000 : 0)); + needFlush = true; + } + + public function setDepth( depth : h3d.mat.Texture ) { + signature.setI32(PSIGN_RENDER_TARGETS, 0x80000000); + needFlush = true; + } + + public function setRenderTargets( textures : Array, depthEnabled : Bool ) { + var bits = 0; + for( i => t in textures ) + bits |= getRTBits(t) << (i << 2); + signature.setI32(PSIGN_RENDER_TARGETS, bits | (depthEnabled ? 0x80000000 : 0)); + needFlush = true; + } + + public function selectMaterial( pass : h3d.mat.Pass ) @:privateAccess { + signature.setI32(PSIGN_MATID, pass.bits); + signature.setUI8(PSIGN_COLOR_MASK, pass.colorMask); + var st = pass.stencil; + if( st != null ) { + signature.setUI16(PSIGN_STENCIL_MASK, st.maskBits & 0xFFFF); + signature.setI32(PSIGN_STENCIL_OPS, st.opBits); + } else { + signature.setUI16(PSIGN_STENCIL_MASK, 0); + signature.setI32(PSIGN_STENCIL_OPS, 0); + } + needFlush = true; + } + + public inline function setBuffer( i : Int, inf : hxd.BufferFormat.BufferMapping ) { + signature.setUI16(PSIGN_LAYOUT + (i<<1), (inf.offset << 1) | inf.precision.toInt()); + needFlush = true; + } + + public function getCurrentPass() @:privateAccess { + var pass = tmpPass; + pass.loadBits(signature.getI32(PSIGN_MATID)); + pass.colorMask = signature.getUI8(PSIGN_COLOR_MASK); + var mask = signature.getUI16(PSIGN_STENCIL_MASK); + var ops = signature.getI32(PSIGN_STENCIL_OPS); + if( ops == 0 ) + pass.stencil = null; + else { + pass.stencil = tmpStencil; + pass.stencil.loadMaskBits(mask); + pass.stencil.loadOpBits(ops); + } + return pass; + } + + public function getBufferInput( i : Int ) { + var b = signature.getUI16(PSIGN_LAYOUT + (i<<1)); + return new hxd.BufferFormat.BufferMapping(i, (b >> 1) & ~3, @:privateAccess new hxd.BufferFormat.Precision(b & 7)); + } + + public function lookup( cache : PipelineCache, inputs : Int ) : CachedPipeline { + needFlush = false; + var signature = signature; + var signatureSize = PSIGN_LAYOUT + (inputs << 1); + adlerOut.setI32(0, 0); + hl.Format.digest(adlerOut, signature, signatureSize, 3); + var hash = adlerOut.getI32(0); + var pipes = cache.get(hash); + if( pipes == null ) { + pipes = new hl.NativeArray(1); + cache.set(hash, pipes); + } + var insert = -1; + for( i in 0...pipes.length ) { + var p = pipes[i]; + if( p == null ) { + insert = i; + break; + } + if( p.size == signatureSize && p.bytes.compare(0, signature, 0, signatureSize) == 0 ) + return p; + } + var signatureBytes = @:privateAccess new haxe.io.Bytes(signature, signatureSize); + if( insert < 0 ) { + var pipes2 = new hl.NativeArray(pipes.length + 1); + pipes2.blit(0, pipes, 0, insert); + cache.set(hash, pipes2); + pipes = pipes2; + } + var cp = new CachedPipeline(); + cp.bytes = signature.sub(0, signatureSize); + cp.size = signatureSize; + pipes[insert] = cp; + return cp; + } + + +} \ No newline at end of file From 00e91ccdba7aaca75d89cc5a0490af313f8cbf9a Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 12 Nov 2023 15:48:01 +0100 Subject: [PATCH 07/15] added pipeline cache buffer stride if needed --- h3d/impl/DX12Driver.hx | 4 +- h3d/impl/PipelineCache.hx | 106 ++++++++++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/h3d/impl/DX12Driver.hx b/h3d/impl/DX12Driver.hx index 59382d72f0..38fe0fa952 100644 --- a/h3d/impl/DX12Driver.hx +++ b/h3d/impl/DX12Driver.hx @@ -1635,7 +1635,7 @@ class DX12Driver extends h3d.impl.Driver { v.sizeInBytes = bview.sizeInBytes; v.strideInBytes = bview.strideInBytes; if( inf.offset >= 256 ) throw "assert"; - pipelineBuilder.setBuffer(i, inf); + pipelineBuilder.setBuffer(i, inf, bview.strideInBytes); } frame.commandList.iaSetVertexBuffers(0, currentShader.inputCount, views[0]); } @@ -1651,7 +1651,7 @@ class DX12Driver extends h3d.impl.Driver { v.sizeInBytes = bview.sizeInBytes; v.strideInBytes = bview.strideInBytes; if( inf.offset >= 256 ) throw "assert"; - pipelineBuilder.setBuffer(i, inf); + pipelineBuilder.setBuffer(i, inf, bview.strideInBytes); } frame.commandList.iaSetVertexBuffers(0, map.length, views[0]); } diff --git a/h3d/impl/PipelineCache.hx b/h3d/impl/PipelineCache.hx index 62af07be67..b9b9b01dc9 100644 --- a/h3d/impl/PipelineCache.hx +++ b/h3d/impl/PipelineCache.hx @@ -1,7 +1,50 @@ package h3d.impl; +#if hl +@:forward(setI32,setUI8,setUI16,getUI8,getUI16,getI32,sub) +private abstract Bytes(hl.Bytes) from hl.Bytes { + public function new(size) this = new hl.Bytes(size); + public inline function compare( bytes : Bytes, size : Int ) { + return this.compare(0, bytes, 0, size); + } +} +#else +@:forward(sub) +private abstract Bytes(haxe.io.Bytes) from haxe.io.Bytes { + public function new(size) { + this = haxe.io.Bytes.alloc(size); + } + public inline function setI32(idx:Int,v:Int) { + this.setInt32(idx, v); + } + public inline function setUI8(idx:Int,v:Int) { + this.set(idx, v); + } + public inline function setUI16(idx:Int,v:Int) { + this.setUInt16(idx, v); + } + public inline function getI32(idx:Int) { + return this.getInt32(idx); + } + public inline function getUI8(idx:Int) { + return this.get(idx); + } + public inline function getUI16(idx:Int) { + return this.getUInt16(idx); + } + public function compare( bytes : Bytes, size : Int ) { + var bytes : haxe.io.Bytes = cast bytes; + for( i in 0...size ) { + var d = this.get(i) - bytes.get(i); + if( d != 0 ) return d; + } + return 0; + } +} +#end + @:generic class CachedPipeline { - public var bytes : hl.Bytes; + public var bytes : Bytes; public var size : Int; public var pipeline : T; public function new() { @@ -9,7 +52,7 @@ package h3d.impl; } @:forward(get,set) -abstract PipelineCache(Map>>) { +abstract PipelineCache(Map>>) { public function new() { this = new Map(); @@ -27,15 +70,19 @@ class PipelineBuilder { static inline var PSIGN_RENDER_TARGETS = PSIGN_STENCIL_OPS + 4; static inline var PSIGN_LAYOUT = PSIGN_RENDER_TARGETS + 8; static inline var MAX_BUFFERS = 8; - static inline var PSIGN_SIZE = PSIGN_LAYOUT + MAX_BUFFERS * 2; + static inline var SHIFT_PER_BUFFER = #if js 2 #else 1 #end; + static inline var PSIGN_SIZE = PSIGN_LAYOUT + (MAX_BUFFERS << SHIFT_PER_BUFFER); public var needFlush : Bool; - var signature = new hl.Bytes(64); - var adlerOut = new hl.Bytes(4); + var signature = new Bytes(64); var tmpPass = new h3d.mat.Pass(""); var tmpStencil = new h3d.mat.Stencil(); + #if hl + var adlerOut = new Bytes(4); + #end public function new() { + if( PSIGN_SIZE > 64 ) throw "assert"; } static function getRTBits( tex : h3d.mat.Texture ) { @@ -95,8 +142,11 @@ class PipelineBuilder { needFlush = true; } - public inline function setBuffer( i : Int, inf : hxd.BufferFormat.BufferMapping ) { - signature.setUI16(PSIGN_LAYOUT + (i<<1), (inf.offset << 1) | inf.precision.toInt()); + public inline function setBuffer( i : Int, inf : hxd.BufferFormat.BufferMapping, stride : Int ) { + signature.setUI16(PSIGN_LAYOUT + (i<> 1) & ~3, @:privateAccess new hxd.BufferFormat.Precision(b & 7)); } - public function lookup( cache : PipelineCache, inputs : Int ) : CachedPipeline { - needFlush = false; - var signature = signature; - var signatureSize = PSIGN_LAYOUT + (inputs << 1); + #if js + public function getBufferStride( i : Int ) { + return signature.getUI16(PSIGN_LAYOUT + (i << SHIFT_PER_BUFFER) + 2); + } + #end + + function hashSign( size : Int ) { + #if hl adlerOut.setI32(0, 0); hl.Format.digest(adlerOut, signature, signatureSize, 3); - var hash = adlerOut.getI32(0); + return adlerOut.getI32(0); + #else + var tot = 0; + for( i in 0...size>>2 ) + tot = (tot * 31 + signature.getI32(i<<2)) % 0x7FFFFFFF; + switch( size & 3 ) { + case 0: + case 2: tot = (tot * 31 + signature.getUI16(size - 2)) % 0x7FFFFFFF; + default: throw "assert"; + } + return tot; + #end + } + + public function lookup( cache : PipelineCache, inputs : Int ) : CachedPipeline { + needFlush = false; + var signatureSize = PSIGN_LAYOUT + (inputs << SHIFT_PER_BUFFER); + var hash = hashSign(signatureSize); var pipes = cache.get(hash); if( pipes == null ) { - pipes = new hl.NativeArray(1); + pipes = #if hl new hl.NativeArray(1) #else [] #end; cache.set(hash, pipes); } var insert = -1; @@ -140,15 +211,18 @@ class PipelineBuilder { insert = i; break; } - if( p.size == signatureSize && p.bytes.compare(0, signature, 0, signatureSize) == 0 ) + if( p.size == signatureSize && p.bytes.compare(signature, signatureSize) == 0 ) return p; } - var signatureBytes = @:privateAccess new haxe.io.Bytes(signature, signatureSize); if( insert < 0 ) { + #if hl var pipes2 = new hl.NativeArray(pipes.length + 1); pipes2.blit(0, pipes, 0, insert); cache.set(hash, pipes2); pipes = pipes2; + #else + insert = pipes.length + 1; + #end } var cp = new CachedPipeline(); cp.bytes = signature.sub(0, signatureSize); From 0da278489485c4434f79c69de321a76beb644f76 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 12 Nov 2023 15:48:18 +0100 Subject: [PATCH 08/15] pipeline builder --- h3d/impl/WebGpuDriver.hx | 114 ++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 31 deletions(-) diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index cfeb63cf2c..24aab37494 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -18,7 +18,8 @@ class WebGpuShader { public var vertex : WebGpuSubShader; public var fragment : WebGpuSubShader; public var layout : GPUPipelineLayout; - public var pipeline : GPURenderPipeline; + public var inputCount : Int; + public var pipelines : PipelineCache = new PipelineCache(); public function new() { } } @@ -49,6 +50,8 @@ class WebGpuDriver extends h3d.impl.Driver { var frames : Array = []; var frame : WebGpuFrame; var frameCount : Int = 0; + var pipelineBuilder = new PipelineCache.PipelineBuilder(); + var curStencilRef : Int; public function new() { inst = this; @@ -115,6 +118,7 @@ class WebGpuDriver extends h3d.impl.Driver { override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0, depthBinding : h3d.Engine.DepthBinding = ReadWrite ) { flushPass(); + pipelineBuilder.setRenderTarget(tex, depthBinding != NotBound); if( tex == null ) { renderPassDesc = { colorAttachments : [{ @@ -135,6 +139,10 @@ class WebGpuDriver extends h3d.impl.Driver { throw "TODO"; } + override function setRenderTargets(textures:Array, depthBinding:h3d.Engine.DepthBinding = ReadWrite) { + pipelineBuilder.setRenderTargets(textures, depthBinding != NotBound); + } + function beginFrame() { if( device == null ) return; frame = frames[(frameCount++)%frames.length]; @@ -159,6 +167,7 @@ class WebGpuDriver extends h3d.impl.Driver { depth.depthClearValue = js.Lib.undefined; depth.stencilClearValue = js.Lib.undefined; } + curStencilRef = -1; needClear = false; } @@ -331,35 +340,19 @@ class WebGpuDriver extends h3d.impl.Driver { sh.vertex = compile(shader.vertex,VERTEX); sh.fragment = compile(shader.fragment,FRAGMENT); sh.layout = device.createPipelineLayout({ bindGroupLayouts: sh.vertex.groups.concat(sh.fragment.groups) }); - - var pipeline = device.createRenderPipeline({ - layout : sh.layout, - vertex : { module : sh.vertex.module, entryPoint : "main", buffers : [ - { - attributes: [{ shaderLocation : 0, offset : 0, format : Float32x3 }], - arrayStride: 4 * 6, - stepMode: GPUVertexStepMode.Vertex - }, - { - attributes: [{ shaderLocation : 1, offset : 3*4, format : Float32x3 }], - arrayStride: 4 * 6, // sizeof(float) * 3 - stepMode: GPUVertexStepMode.Vertex - } - ]}, - fragment : { module : sh.fragment.module, entryPoint : "main", targets : [{ format : Bgra8unorm }] }, - primitive : { frontFace : CW, cullMode : None, topology : Triangle_list }, - depthStencil : { - depthWriteEnabled: true, - depthCompare: Less, - format: Depth24plus_stencil8 - } - }); - - sh.pipeline = pipeline; - + sh.inputCount = format.length; return sh; } + override function selectMaterial( pass : h3d.mat.Pass ) { + pipelineBuilder.selectMaterial(pass); + var st = pass.stencil; + if( st != null && curStencilRef != st.reference ) { + curStencilRef = st.reference; + renderPass.setStencilReference(st.reference); + } + } + override function selectShader( shader : hxsl.RuntimeShader ) { var sh = shadersCache.get(shader.id); if( sh == null ) { @@ -370,13 +363,16 @@ class WebGpuDriver extends h3d.impl.Driver { return false; currentShader = sh; beginPass(); - renderPass.setPipeline(sh.pipeline); + pipelineBuilder.setShader(shader); return true; } - override function selectBuffer(b:Buffer) { - for( i in 0...@:privateAccess currentShader.format.inputs.length ) - renderPass.setVertexBuffer(i, b.vbuf); + override function selectBuffer(buffer:Buffer) { + var map = buffer.format.resolveMapping(currentShader.format); + for( i in 0...currentShader.inputCount ) { + renderPass.setVertexBuffer(i, buffer.vbuf); + pipelineBuilder.setBuffer(i, map[i], buffer.format.strideBytes); + } } override function uploadShaderBuffers(buffers:h3d.shader.Buffers, which:h3d.shader.Buffers.BufferKind) { @@ -423,11 +419,67 @@ class WebGpuDriver extends h3d.impl.Driver { } } + function flushPipeline() { + if( !pipelineBuilder.needFlush ) return; + var cache = pipelineBuilder.lookup(currentShader.pipelines, currentShader.inputCount); + if( cache.pipeline == null ) + cache.pipeline = makePipeline(currentShader); + renderPass.setPipeline(cache.pipeline); + } + + function makePipeline( sh : WebGpuShader ) { + var buffers : Array = []; + var targets = [{ format : Bgra8unorm }]; + var pass = pipelineBuilder.getCurrentPass(); + for( i in 0...sh.inputCount ) { + var inf = pipelineBuilder.getBufferInput(i); + var type = @:privateAccess currentShader.format.inputs[i].type; + if( inf.precision != F32 ) throw "TODO"; + buffers.push({ + attributes: [{ shaderLocation : i, offset : inf.offset, format : + switch( type ) { + case DFloat: Float32; + case DVec2: Float32x2; + case DVec3: Float32x3; + case DVec4: Float32x4; + case DBytes4: Uint8x4; + } + }], + arrayStride : pipelineBuilder.getBufferStride(i), + stepMode: GPUVertexStepMode.Vertex, + }); + } + var pipeline = device.createRenderPipeline({ + layout : sh.layout, + vertex : { module : sh.vertex.module, entryPoint : "main", buffers : buffers }, + fragment : { module : sh.fragment.module, entryPoint : "main", targets : targets }, + primitive : { frontFace : CW, cullMode : switch( pass.culling ) { case None, Both: None; case Back: Back; case Front: Front; }, topology : Triangle_list }, + depthStencil : { + depthWriteEnabled: pass.depthWrite, + depthCompare: COMPARE[pass.depthTest.getIndex()], + format: Depth24plus_stencil8 + } + }); + return pipeline; + } + override function draw(ibuf:IndexBuffer, startIndex:Int, ntriangles:Int) { + flushPipeline(); renderPass.setIndexBuffer(ibuf.buf, ibuf.stride==2?Uint16:Uint32, startIndex*ibuf.stride); renderPass.drawIndexed(ntriangles*3); } + static var COMPARE : Array = [ + Always, + Never, + Equal, + Not_equal, + Greater, + Greater_equal, + Less, + Less_equal, + ]; + static var inst : WebGpuDriver; static function checkReady() { return true; From 2862a99f8dcce5cfb5bac251f68e06b37d12caf7 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 12 Nov 2023 16:16:08 +0100 Subject: [PATCH 09/15] blend ok --- h3d/impl/WebGpuDriver.hx | 42 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 24aab37494..55cd6ea7f2 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -8,7 +8,7 @@ class WebGpuSubShader { public var kind : GPUShaderStage; public var module : GPUShaderModule; public var groups : Array; - public var paramsBufferSize : Int; + public var paramsBufferSize : Int = 0; public function new() { } } @@ -387,7 +387,7 @@ class WebGpuDriver extends h3d.impl.Driver { return; throw "TODO"; case Params: - if( buffers.params.length == 0 ) + if( sh.paramsBufferSize == 0 ) return; var flags = new haxe.EnumFlags(); var buffer = device.createBuffer({ @@ -429,8 +429,23 @@ class WebGpuDriver extends h3d.impl.Driver { function makePipeline( sh : WebGpuShader ) { var buffers : Array = []; - var targets = [{ format : Bgra8unorm }]; var pass = pipelineBuilder.getCurrentPass(); + var targets = [{ + format : Bgra8unorm, + writeMask : pass.colorMask, + blend : { + color : { + operation : OP[pass.blendOp.getIndex()], + srcFactor : BLEND[pass.blendSrc.getIndex()], + dstFactor : BLEND[pass.blendDst.getIndex()], + }, + alpha : { + operation : OP[pass.blendAlphaOp.getIndex()], + srcFactor : BLEND[pass.blendAlphaSrc.getIndex()], + dstFactor : BLEND[pass.blendAlphaDst.getIndex()], + }, + }, + }]; for( i in 0...sh.inputCount ) { var inf = pipelineBuilder.getBufferInput(i); var type = @:privateAccess currentShader.format.inputs[i].type; @@ -480,6 +495,27 @@ class WebGpuDriver extends h3d.impl.Driver { Less_equal, ]; + static var OP : Array = [ + Add, + Subtract, + Reverse_subtract, + Min, + Max, + ]; + + static var BLEND : Array = [ + One, + Zero, + Src_alpha, + Src, + Dst_alpha, + Dst, + One_minus_src_alpha, + One_minus_src, + One_minus_dst_alpha, + One_minus_dst, + ]; + static var inst : WebGpuDriver; static function checkReady() { return true; From bb83ba2b917418b4c50fff6710ed3fe726685c7c Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sat, 9 Dec 2023 16:20:01 +0100 Subject: [PATCH 10/15] fixed binding groups for params/globals --- h3d/impl/WebGpuDriver.hx | 60 +++++++++++++++++++++++----------------- hxsl/WgslOut.hx | 12 ++++---- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 55cd6ea7f2..412d9a2d0f 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -7,8 +7,10 @@ import h3d.mat.Pass; class WebGpuSubShader { public var kind : GPUShaderStage; public var module : GPUShaderModule; - public var groups : Array; public var paramsBufferSize : Int = 0; + public var globalsBufferSize : Int = 0; + public var paramsGroup : Int = 0; + public var globalsGroup : Int = 0; public function new() { } } @@ -20,6 +22,7 @@ class WebGpuShader { public var layout : GPUPipelineLayout; public var inputCount : Int; public var pipelines : PipelineCache = new PipelineCache(); + public var groups : Array; public function new() { } } @@ -296,20 +299,17 @@ class WebGpuDriver extends h3d.impl.Driver { buf.unmap(); } - - function compile( shader : hxsl.RuntimeShader.RuntimeShaderData, kind ) { + function compile( parent : WebGpuShader, shader : hxsl.RuntimeShader.RuntimeShaderData, kind ) { var comp = new hxsl.WgslOut(); - var source = comp.run(shader.data); var sh = new WebGpuSubShader(); sh.kind = kind; - sh.module = device.createShaderModule({ code : source }); - sh.groups = []; for( v in shader.data.vars ) { switch( v.kind ) { - case Param: + case Param, Global: var size = hxsl.Ast.Tools.size(v.type) * 4; + var index = parent.groups.length; var g = device.createBindGroupLayout({ entries : [{ - binding: 0, + binding: index, visibility : kind, buffer : { type : Uniform, @@ -317,11 +317,23 @@ class WebGpuDriver extends h3d.impl.Driver { minBindingSize: size, } }]}); - sh.paramsBufferSize = size; - sh.groups.push(g); + switch( v.kind ) { + case Param: + sh.paramsBufferSize = size; + sh.paramsGroup = index; + comp.paramsBinding = comp.paramsGroup = index; + case Global: + sh.globalsBufferSize = size; + sh.globalsGroup = index; + comp.globalsBinding = comp.globalsGroup = index; + default: + } + parent.groups.push(g); default: } } + var source = comp.run(shader.data); + sh.module = device.createShaderModule({ code : source }); return sh; } @@ -336,10 +348,11 @@ class WebGpuDriver extends h3d.impl.Driver { default: } } + sh.groups = []; sh.format = hxd.BufferFormat.make(format); - sh.vertex = compile(shader.vertex,VERTEX); - sh.fragment = compile(shader.fragment,FRAGMENT); - sh.layout = device.createPipelineLayout({ bindGroupLayouts: sh.vertex.groups.concat(sh.fragment.groups) }); + sh.vertex = compile(sh,shader.vertex,VERTEX); + sh.fragment = compile(sh,shader.fragment,FRAGMENT); + sh.layout = device.createPipelineLayout({ bindGroupLayouts: sh.groups }); sh.inputCount = format.length; return sh; } @@ -382,32 +395,29 @@ class WebGpuDriver extends h3d.impl.Driver { function _uploadShaderBuffers(buffers:h3d.shader.Buffers.ShaderBuffers, which:h3d.shader.Buffers.BufferKind, sh:WebGpuSubShader) { switch( which ) { - case Globals: - if( buffers.globals.length == 0 ) - return; - throw "TODO"; - case Params: - if( sh.paramsBufferSize == 0 ) + case Globals, Params: + var size = which == Params ? sh.paramsBufferSize : sh.globalsBufferSize; + if( size == 0 ) return; - var flags = new haxe.EnumFlags(); var buffer = device.createBuffer({ - size : sh.paramsBufferSize, + size : size, usage : UNIFORM, mappedAtCreation : true, }); var map = buffer.getMappedRange(); - new js.lib.Float32Array(map).set(cast buffers.params); + new js.lib.Float32Array(map).set(cast (which == Params ? buffers.params : buffers.globals)); buffer.unmap(); + var index = which == Params ? sh.paramsGroup : sh.globalsGroup; var group = device.createBindGroup({ - layout : sh.groups[0], + layout : currentShader.groups[index], entries: [{ - binding: 0, + binding: index, resource: { buffer : buffer, } }], }); - renderPass.setBindGroup(0, group); + renderPass.setBindGroup(index, group); case Textures: if( buffers.tex.length == 0 ) return; diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index 11a662e836..a7b23fd493 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -26,6 +26,10 @@ class WgslOut { var allNames : Map; var hasVarying : Bool; public var varNames : Map; + public var paramsGroup : Int = 0; + public var paramsBinding : Int = 0; + public var globalsGroup : Int = 0; + public var globalsBinding : Int = 0; var varAccess : Map; @@ -454,14 +458,12 @@ class WgslOut { if( !found ) return; - add('struct _globals {\n'); for( v in s.vars ) if( v.kind == Global ) { - add("\t"); + add('@group(${globalsGroup}) @binding(${globalsBinding}) var '); addVar(v); - add(",\n"); + add(";\n"); } - add("};\n\n"); } function initParams( s : ShaderData ) { @@ -491,7 +493,7 @@ class WgslOut { continue; } } - add("@group(0) @binding(0) var "); + add('@group($paramsGroup) @binding($paramsBinding) var '); addVar(v); add(";\n"); } From 63e953b9c9b396f6f950563fe631af69bc5021c9 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 13 Oct 2024 15:54:20 +0200 Subject: [PATCH 11/15] textures ok --- h3d/impl/Driver.hx | 2 +- h3d/impl/WebGpuDriver.hx | 127 ++++++++++++++++++++++++++++++++++----- hxsl/WgslOut.hx | 76 ++++++++++++++++++----- 3 files changed, 176 insertions(+), 29 deletions(-) diff --git a/h3d/impl/Driver.hx b/h3d/impl/Driver.hx index 4c50257cff..3fc2d9fae1 100644 --- a/h3d/impl/Driver.hx +++ b/h3d/impl/Driver.hx @@ -8,7 +8,7 @@ typedef Query = {}; #elseif (js && webgpu) typedef IndexBuffer = { buf : WebGpuApi.GPU_Buffer, stride : Int }; typedef GPUBuffer = WebGpuApi.GPU_Buffer; -typedef Texture = {}; +typedef Texture = { tex : WebGpuApi.GPUTexture, view : WebGpuApi.GPUTextureView }; typedef DepthBuffer = {}; typedef Query = {}; #elseif js diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 412d9a2d0f..3d3bfd2970 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -3,6 +3,7 @@ package h3d.impl; import h3d.impl.Driver; import h3d.impl.WebGpuApi; import h3d.mat.Pass; +using hxsl.Ast; class WebGpuSubShader { public var kind : GPUShaderStage; @@ -11,6 +12,8 @@ class WebGpuSubShader { public var globalsBufferSize : Int = 0; public var paramsGroup : Int = 0; public var globalsGroup : Int = 0; + public var texturesGroup : Int = 0; + public var texturesCount : Int; public function new() { } } @@ -237,6 +240,19 @@ class WebGpuDriver extends h3d.impl.Driver { return _allocBuffer(VERTEX,b.vertices,b.format.strideBytes); } + override function allocTexture(t:h3d.mat.Texture):Texture { + var tex = device.createTexture({ + size : { width : t.width, height : t.height }, + mipLevelCount : t.mipLevels, + format : switch( t.format ) { + case RGBA: Rgba8unorm; + default: throw "Unsupported texture format "+t.format; + }, + usage : TEXTURE_BINDING | COPY_DST, + }); + return { tex : tex, view : tex.createView() }; + } + override function allocIndexes(count:Int, is32:Bool):IndexBuffer { var stride = is32?4:2; var buf = _allocBuffer(INDEX,count,stride); @@ -299,40 +315,95 @@ class WebGpuDriver extends h3d.impl.Driver { buf.unmap(); } - function compile( parent : WebGpuShader, shader : hxsl.RuntimeShader.RuntimeShaderData, kind ) { + override function uploadTextureBitmap(t:h3d.mat.Texture, bmp:hxd.BitmapData, mipLevel:Int, side:Int) { + throw "Not implemented"; + } + + override function uploadTexturePixels(t:h3d.mat.Texture, pixels:hxd.Pixels, mipLevel:Int, side:Int) { + var bpp = hxd.Pixels.calcStride(pixels.width,pixels.format); + device.queue.writeTexture({ texture : t.t.tex, mipLevel : mipLevel, origin : side == 0 ? js.Lib.undefined : { z : side } }, pixels.bytes.getData(), { bytesPerRow : bpp }, { width : pixels.width, height : pixels.height }); + } + + function isSampler(t) { + return switch( t ) { + case TArray(t,_) if( t.isSampler() ): true; + default: t.isSampler(); + } + } + + function compileShaderPart( shader : hxsl.RuntimeShader.RuntimeShaderData, groups : Array>, kind ) { var comp = new hxsl.WgslOut(); var sh = new WebGpuSubShader(); + var textures = []; sh.kind = kind; for( v in shader.data.vars ) { switch( v.kind ) { case Param, Global: + switch(v.type) { + case TArray(t, SConst(n)) if( t.isSampler() ): + for( i in 0...n ) + textures.push(t); + continue; + case t if( t.isSampler() ): + textures.push(t); + continue; + default: + } + var index = groups.length; var size = hxsl.Ast.Tools.size(v.type) * 4; - var index = parent.groups.length; - var g = device.createBindGroupLayout({ entries : [{ - binding: index, + groups.push([{ + binding: 0, visibility : kind, buffer : { type : Uniform, hasDynamicOffset : false, minBindingSize: size, } - }]}); + }]); switch( v.kind ) { case Param: sh.paramsBufferSize = size; sh.paramsGroup = index; - comp.paramsBinding = comp.paramsGroup = index; + comp.paramsGroup = index; case Global: sh.globalsBufferSize = size; sh.globalsGroup = index; - comp.globalsBinding = comp.globalsGroup = index; + comp.globalsGroup = index; default: } - parent.groups.push(g); default: } } + + if( textures.length > 0 ) { + var index = groups.length; + groups.push([ + for( t in textures ) { + binding : 0, + visibility : kind, + texture: { + sampleType : Float, + viewDimension : D2, + } + } + ]); + groups.push([ + for( t in textures ) { + binding : 0, + visibility : kind, + sampler: { + type : Filtering, + } + } + ]); + sh.texturesGroup = comp.texturesGroup = index; + } + sh.texturesCount = textures.length; + var source = comp.run(shader.data); + #if debug_shaders + trace(source); + #end sh.module = device.createShaderModule({ code : source }); return sh; } @@ -348,10 +419,14 @@ class WebGpuDriver extends h3d.impl.Driver { default: } } - sh.groups = []; + var groupsDecls = []; sh.format = hxd.BufferFormat.make(format); - sh.vertex = compile(sh,shader.vertex,VERTEX); - sh.fragment = compile(sh,shader.fragment,FRAGMENT); + sh.vertex = compileShaderPart(shader.vertex,groupsDecls,VERTEX); + sh.fragment = compileShaderPart(shader.fragment,groupsDecls,FRAGMENT); + #if debug_shaders + trace(groupsDecls); + #end + sh.groups = [for( g in groupsDecls ) device.createBindGroupLayout({ entries: g })]; sh.layout = device.createPipelineLayout({ bindGroupLayouts: sh.groups }); sh.inputCount = format.length; return sh; @@ -393,6 +468,10 @@ class WebGpuDriver extends h3d.impl.Driver { _uploadShaderBuffers(buffers.fragment, which, currentShader.fragment); } + function createSampler( t : h3d.mat.Texture ) { + return device.createSampler(); + } + function _uploadShaderBuffers(buffers:h3d.shader.Buffers.ShaderBuffers, which:h3d.shader.Buffers.BufferKind, sh:WebGpuSubShader) { switch( which ) { case Globals, Params: @@ -411,7 +490,7 @@ class WebGpuDriver extends h3d.impl.Driver { var group = device.createBindGroup({ layout : currentShader.groups[index], entries: [{ - binding: index, + binding: 0, resource: { buffer : buffer, } @@ -419,9 +498,29 @@ class WebGpuDriver extends h3d.impl.Driver { }); renderPass.setBindGroup(index, group); case Textures: - if( buffers.tex.length == 0 ) + if( sh.texturesCount == 0 ) return; - throw "TODO"; + var index = sh.texturesGroup; + var group = device.createBindGroup({ + layout : currentShader.groups[index], + entries: [ + for( i in 0...buffers.tex.length ) { + binding : i, + resource : buffers.tex[i].t.view, + } + ] + }); + var samplers = device.createBindGroup({ + layout : currentShader.groups[index+1], + entries: [ + for( i in 0...buffers.tex.length ) { + binding : i, + resource : createSampler(buffers.tex[i]), + } + ] + }); + renderPass.setBindGroup(index, group); + renderPass.setBindGroup(index+1, samplers); case Buffers: if( buffers.buffers == null || buffers.buffers.length == 0 ) return; diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index a7b23fd493..1a7fee482a 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -26,10 +26,9 @@ class WgslOut { var allNames : Map; var hasVarying : Bool; public var varNames : Map; - public var paramsGroup : Int = 0; - public var paramsBinding : Int = 0; - public var globalsGroup : Int = 0; - public var globalsBinding : Int = 0; + public var paramsGroup : Int = -1; + public var globalsGroup : Int = -1; + public var texturesGroup : Int = -1; var varAccess : Map; @@ -86,11 +85,11 @@ class WgslOut { case TMat3x4: add("mat3x4f"); case TSampler2D: - add("Texture2D"); + add("texture_2d"); case TSamplerCube: - add("TextureCube"); + add("texture_cube"); case TSampler2DArray: - add("Texture2DArray"); + add("texture_2d_array"); case TStruct(vl): add("struct { "); for( v in vl ) { @@ -275,6 +274,27 @@ class WgslOut { } else { add("/*var*/"); } + case TCall({ e : TGlobal(g) },args): + switch( [g,args] ) { + case [Texture,[t,uv]]: + add("textureSample("); + addExpr(t, tabs); + add(","); + addExpr(t, tabs); + add("__sampler"); + add(","); + addExpr(uv, tabs); + add(")"); + default: + add(GLOBALS.get(g)); + add("("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + addValue(e, tabs); + } + add(")"); + } case TCall(e, args): addValue(e, tabs); add("("); @@ -346,7 +366,10 @@ class WgslOut { case TBreak: add("break"); case TArray(e, index): - switch( e.t ) { + switch( [e.t, index.e] ) { + case [TArray(et,_),TConst(CInt(idx))] if( et.isSampler() ): + addValue(e, tabs); + add(idx); default: addValue(e, tabs); add("["); @@ -458,11 +481,13 @@ class WgslOut { if( !found ) return; + var globals = 0; for( v in s.vars ) if( v.kind == Global ) { - add('@group(${globalsGroup}) @binding(${globalsBinding}) var '); + add('@group(${globalsGroup}) @binding($globals) var '); addVar(v); add(";\n"); + globals++; } } @@ -478,6 +503,7 @@ class WgslOut { var textures = []; var buffers = []; + var params = 0; for( v in s.vars ) if( v.kind == Param ) { switch( v.type ) { @@ -493,9 +519,10 @@ class WgslOut { continue; } } - add('@group($paramsGroup) @binding($paramsBinding) var '); + add('@group($paramsGroup) @binding($params) var '); addVar(v); add(";\n"); + params++; } var bufCount = 0; @@ -509,11 +536,32 @@ class WgslOut { var texCount = 0; for( v in textures ) { - addVar(v); - add(' : register(t${texCount});\n'); switch( v.type ) { - case TArray(_,SConst(n)): texCount += n; - default: texCount++; + case TArray(t,SConst(n)): + // array of textures not supported in WSGL (so annoying...) + for( i in 0...n ) { + add('@group($texturesGroup) @binding($texCount) var '); + add(v.name+i); + add(" : "); + addType(t); + add(";\n"); + + add('@group(${texturesGroup+1}) @binding($texCount) var '); + add(v.name+i+"__sampler"); + add(" : sampler"); + add(";\n"); + texCount++; + } + default: + add('@group($texturesGroup) @binding($texCount) var '); + addVar(v); + add(";\n"); + + add('@group(${texturesGroup+1}) @binding($texCount) var '); + add(v.name+"__sampler"); + add(" : sampler"); + add(";\n"); + texCount++; } } } From 23163f70024471ca53ca86795cd3dd4d9fa1846b Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 13 Oct 2024 21:42:48 +0200 Subject: [PATCH 12/15] minor fixes --- h3d/impl/WebGpuDriver.hx | 14 ++++++++++---- hxsl/WgslOut.hx | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 3d3bfd2970..a91de3cd37 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -316,7 +316,9 @@ class WebGpuDriver extends h3d.impl.Driver { } override function uploadTextureBitmap(t:h3d.mat.Texture, bmp:hxd.BitmapData, mipLevel:Int, side:Int) { - throw "Not implemented"; + var pixels = bmp.getPixels(); + uploadTexturePixels(t, pixels, mipLevel, side); + pixels.dispose(); } override function uploadTexturePixels(t:h3d.mat.Texture, pixels:hxd.Pixels, mipLevel:Int, side:Int) { @@ -504,16 +506,20 @@ class WebGpuDriver extends h3d.impl.Driver { var group = device.createBindGroup({ layout : currentShader.groups[index], entries: [ - for( i in 0...buffers.tex.length ) { + for( i in 0...sh.texturesCount ) { binding : i, - resource : buffers.tex[i].t.view, + resource : { + var t = buffers.tex[i]; + if( t.t == null && t.realloc != null ) t.realloc(); + t.t.view; + } } ] }); var samplers = device.createBindGroup({ layout : currentShader.groups[index+1], entries: [ - for( i in 0...buffers.tex.length ) { + for( i in 0...sh.texturesCount ) { binding : i, resource : createSampler(buffers.tex[i]), } diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index 1a7fee482a..cf435fd5cb 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -607,6 +607,7 @@ class WgslOut { var locals = Lambda.array(locals); locals.sort(function(v1, v2) return Reflect.compare(v1.name, v2.name)); for( v in locals ) { + add("var "); addVar(v); add(";\n"); } From 294f50f7e61cc151825e54a06e6db7feb868b3d6 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Tue, 15 Oct 2024 08:51:12 +0200 Subject: [PATCH 13/15] fixed swizzle write --- hxsl/WgslOut.hx | 51 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index cf435fd5cb..d7bfa8c656 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -246,15 +246,50 @@ class WgslOut { addValue(e,tabs); } add("}"); - case TBinop(op, e1, e2): - switch( [op, e1.t, e2.t] ) { - default: - addValue(e1, tabs); - add(" "); - add(Printer.opStr(op)); - add(" "); - addValue(e2, tabs); + case TBinop(op = OpAssign|OpAssignOp(_), { e : TSwiz(e,regs) }, e2) if( regs.length > 1 ): + // WSGL does not support swizzle writing outside of a single component (wth) + addValue(e, tabs); + add(" = "); + var size = switch(e.t) { + case TVec(size, _): size; + default: throw "assert"; } + add("vec"+size+"("); + for( i in 0...size ) { + if( i > 0 ) add(","); + var found = false; + for( j => r in regs ) { + if( r.getIndex() == i ) { + found = true; + addValue(e2,tabs); + add("."); + add("xyzw".charAt(j)); + switch( op ) { + case OpAssignOp(op): + add(" "); + add(Printer.opStr(op)); + add(" "); + addValue(e,tabs); + add("."); + add("xyzw".charAt(j)); + default: + } + break; + } + } + if( !found ) { + addValue(e,tabs); + add("."); + add("xyzw".charAt(i)); + } + } + add(")"); + case TBinop(op, e1, e2): + addValue(e1, tabs); + add(" "); + add(Printer.opStr(op)); + add(" "); + addValue(e2, tabs); case TUnop(op, e1): add(switch(op) { case OpNot: "!"; From a5c73dd86431c0671ccdc19e4d2160c62394f5a7 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Tue, 15 Oct 2024 08:52:25 +0200 Subject: [PATCH 14/15] implement sampler --- h3d/impl/WebGpuDriver.hx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index a91de3cd37..64ec491bd6 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -471,7 +471,24 @@ class WebGpuDriver extends h3d.impl.Driver { } function createSampler( t : h3d.mat.Texture ) { - return device.createSampler(); + var mode : GPUAddressMode = switch( t.wrap ) { + case Clamp: Clamp_to_edge; + case Repeat: Repeat; + } + var filter : GPUFilterMode = switch( t.filter ) { + case Nearest: Nearest; + case Linear: Linear; + }; + // no lod bias support ! + return device.createSampler({ + addressModeU : mode, + addressModeV : mode, + addressModeW : mode, + magFilter : filter, + minFilter : filter, + mipmapFilter: t.mipLevels > 1 && t.mipMap == Linear ? Linear : Nearest, + lodMinClamp: t.startingMip, + }); } function _uploadShaderBuffers(buffers:h3d.shader.Buffers.ShaderBuffers, which:h3d.shader.Buffers.BufferKind, sh:WebGpuSubShader) { From 9da28119f1bd0a64f970f010f5c1fa23aa7219a8 Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Tue, 15 Oct 2024 12:00:02 +0200 Subject: [PATCH 15/15] more webgpu support --- h3d/impl/Driver.hx | 2 +- h3d/impl/PipelineCache.hx | 4 ++ h3d/impl/WebGpuDriver.hx | 116 +++++++++++++++++++++++++++++--------- hxsl/WgslOut.hx | 51 ++++++++++++----- 4 files changed, 129 insertions(+), 44 deletions(-) diff --git a/h3d/impl/Driver.hx b/h3d/impl/Driver.hx index 3fc2d9fae1..e0de6bd71f 100644 --- a/h3d/impl/Driver.hx +++ b/h3d/impl/Driver.hx @@ -8,7 +8,7 @@ typedef Query = {}; #elseif (js && webgpu) typedef IndexBuffer = { buf : WebGpuApi.GPU_Buffer, stride : Int }; typedef GPUBuffer = WebGpuApi.GPU_Buffer; -typedef Texture = { tex : WebGpuApi.GPUTexture, view : WebGpuApi.GPUTextureView }; +typedef Texture = { tex : WebGpuApi.GPUTexture, view : WebGpuApi.GPUTextureView, ?views : Array }; typedef DepthBuffer = {}; typedef Query = {}; #elseif js diff --git a/h3d/impl/PipelineCache.hx b/h3d/impl/PipelineCache.hx index b9b9b01dc9..830ec35680 100644 --- a/h3d/impl/PipelineCache.hx +++ b/h3d/impl/PipelineCache.hx @@ -115,6 +115,10 @@ class PipelineBuilder { needFlush = true; } + public function getDepthEnabled() { + return signature.getI32(PSIGN_RENDER_TARGETS) & 0x80000000 != 0; + } + public function setDepth( depth : h3d.mat.Texture ) { signature.setI32(PSIGN_RENDER_TARGETS, 0x80000000); needFlush = true; diff --git a/h3d/impl/WebGpuDriver.hx b/h3d/impl/WebGpuDriver.hx index 64ec491bd6..a7235a19c6 100644 --- a/h3d/impl/WebGpuDriver.hx +++ b/h3d/impl/WebGpuDriver.hx @@ -58,6 +58,7 @@ class WebGpuDriver extends h3d.impl.Driver { var frameCount : Int = 0; var pipelineBuilder = new PipelineCache.PipelineBuilder(); var curStencilRef : Int; + var currentRenderTargets : Array; public function new() { inst = this; @@ -122,10 +123,20 @@ class WebGpuDriver extends h3d.impl.Driver { beginFrame(); } + function createTexView( t : h3d.mat.Texture, layer, mip ) { + var index = layer + mip * t.layerCount; + if( t.t.views == null ) t.t.views = []; + if( t.t.views[index] == null ) t.t.views[index] = t.t.tex.createView({ baseMipLevel : mip, mipLevelCount : 1, baseArrayLayer : layer, arrayLayerCount : 1 }); + return t.t.views[index]; + } + override function setRenderTarget(tex:Null, layer:Int = 0, mipLevel:Int = 0, depthBinding : h3d.Engine.DepthBinding = ReadWrite ) { flushPass(); + if( tex != null && tex.depthBuffer == null ) + depthBinding = NotBound; pipelineBuilder.setRenderTarget(tex, depthBinding != NotBound); if( tex == null ) { + currentRenderTargets = null; renderPassDesc = { colorAttachments : [{ view : frame.colorView, @@ -142,11 +153,46 @@ class WebGpuDriver extends h3d.impl.Driver { }; return; } - throw "TODO"; + currentRenderTargets = [tex]; + renderPassDesc = { + colorAttachments : [{ + view : layer == 0 && mipLevel == 0 ? tex.t.view : createTexView(tex,layer,mipLevel), + loadOp : Load, + storeOp: Store + }], + }; + if( tex.depthBuffer != null && depthBinding != NotBound ) + renderPassDesc.depthStencilAttachment = { + view : tex.depthBuffer.t.view, + depthLoadOp: Load, + depthStoreOp: Store, + stencilLoadOp: Load, + stencilStoreOp: Store, + }; } override function setRenderTargets(textures:Array, depthBinding:h3d.Engine.DepthBinding = ReadWrite) { + flushPass(); + var tex = textures[0]; + if( tex != null && tex.depthBuffer == null ) + depthBinding = NotBound; pipelineBuilder.setRenderTargets(textures, depthBinding != NotBound); + currentRenderTargets = textures.copy(); + renderPassDesc = { + colorAttachments : [for( t in textures ) { + view : t.t.view, + loadOp : Load, + storeOp: Store + }], + }; + if( tex.depthBuffer != null && depthBinding != NotBound ) + renderPassDesc.depthStencilAttachment = { + view : tex.depthBuffer.t.view, + depthLoadOp: Load, + depthStoreOp: Store, + stencilLoadOp: Load, + stencilStoreOp: Store, + }; } function beginFrame() { @@ -178,6 +224,7 @@ class WebGpuDriver extends h3d.impl.Driver { } function flushPass() { + var isClear = needClear; if( needClear ) { if( renderPass != null ) throw "assert"; beginPass(); @@ -240,17 +287,25 @@ class WebGpuDriver extends h3d.impl.Driver { return _allocBuffer(VERTEX,b.vertices,b.format.strideBytes); } + function getTexFormat( t : h3d.mat.Texture ) : GPUTextureFormat { + return switch( t.format ) { + case RGBA: Rgba8unorm; + default: throw "Unsupported texture format "+t.format; + } + } + override function allocTexture(t:h3d.mat.Texture):Texture { + var flags : GPUTextureUsageFlags = TEXTURE_BINDING | COPY_DST; + if( t.flags.has(Target) ) + flags |= RENDER_ATTACHMENT; var tex = device.createTexture({ - size : { width : t.width, height : t.height }, + size : { width : t.width, height : t.height, depthOrArrayLayers : t.layerCount }, mipLevelCount : t.mipLevels, - format : switch( t.format ) { - case RGBA: Rgba8unorm; - default: throw "Unsupported texture format "+t.format; - }, - usage : TEXTURE_BINDING | COPY_DST, + format : getTexFormat(t), + dimension : D2, + usage : flags, }); - return { tex : tex, view : tex.createView() }; + return { tex : tex, view : tex.createView({ dimension: t.flags.has(Cube) ? Cube : (t.layerCount > 1 ? D2_array : D2) }) }; } override function allocIndexes(count:Int, is32:Bool):IndexBuffer { @@ -289,22 +344,6 @@ class WebGpuDriver extends h3d.impl.Driver { } function uploadBuffer(buf:GPU_Buffer,stride:Int,start:Int,count:Int,data:Dynamic,bufPos:Int) { - /* - var size = ((count * stride) + 3) & ~3; - var tmpBuf = device.createBuffer({ - size : size, - usage : (MAP_WRITE:GPUBufferUsageFlags) | COPY_SRC, - mappedAtCreation : true, - }); - new js.lib.Uint8Array(tmpBuf.getMappedRange()).set(data, bufPos); - tmpBuf.unmap(); - // copy - if( commandUpload == null ) - commandUpload = device.createCommandEncoder(); - commandUpload.copyBufferToBuffer(tmpBuf,0,buf,start*stride,size); - // delete later - frame.toDelete.push(tmpBuf); - */ var map = buf.getMappedRange(); if( data is js.lib.Uint16Array ) new js.lib.Uint16Array(map).set(data,bufPos); @@ -385,7 +424,12 @@ class WebGpuDriver extends h3d.impl.Driver { visibility : kind, texture: { sampleType : Float, - viewDimension : D2, + viewDimension : switch( t ) { + case TSampler2D: D2; + case TSamplerCube: Cube; + case TSampler2DArray: D2_array; + default: throw "Unsupported texture type "+t; + }, } } ]); @@ -441,6 +485,7 @@ class WebGpuDriver extends h3d.impl.Driver { curStencilRef = st.reference; renderPass.setStencilReference(st.reference); } + beginPass(); } override function selectShader( shader : hxsl.RuntimeShader ) { @@ -562,7 +607,7 @@ class WebGpuDriver extends h3d.impl.Driver { function makePipeline( sh : WebGpuShader ) { var buffers : Array = []; var pass = pipelineBuilder.getCurrentPass(); - var targets = [{ + var targets = currentRenderTargets == null ? [{ format : Bgra8unorm, writeMask : pass.colorMask, blend : { @@ -577,6 +622,21 @@ class WebGpuDriver extends h3d.impl.Driver { dstFactor : BLEND[pass.blendAlphaDst.getIndex()], }, }, + }] : [for( t in currentRenderTargets ) { + format : getTexFormat(t), + writeMask : pass.colorMask, + blend : { + color : { + operation : OP[pass.blendOp.getIndex()], + srcFactor : BLEND[pass.blendSrc.getIndex()], + dstFactor : BLEND[pass.blendDst.getIndex()], + }, + alpha : { + operation : OP[pass.blendAlphaOp.getIndex()], + srcFactor : BLEND[pass.blendAlphaSrc.getIndex()], + dstFactor : BLEND[pass.blendAlphaDst.getIndex()], + }, + }, }]; for( i in 0...sh.inputCount ) { var inf = pipelineBuilder.getBufferInput(i); @@ -601,11 +661,11 @@ class WebGpuDriver extends h3d.impl.Driver { vertex : { module : sh.vertex.module, entryPoint : "main", buffers : buffers }, fragment : { module : sh.fragment.module, entryPoint : "main", targets : targets }, primitive : { frontFace : CW, cullMode : switch( pass.culling ) { case None, Both: None; case Back: Back; case Front: Front; }, topology : Triangle_list }, - depthStencil : { + depthStencil : currentRenderTargets == null || pipelineBuilder.getDepthEnabled() ? { depthWriteEnabled: pass.depthWrite, depthCompare: COMPARE[pass.depthTest.getIndex()], format: Depth24plus_stencil8 - } + } : js.Lib.undefined, }); return pipeline; } diff --git a/hxsl/WgslOut.hx b/hxsl/WgslOut.hx index d7bfa8c656..b4e6f397f7 100644 --- a/hxsl/WgslOut.hx +++ b/hxsl/WgslOut.hx @@ -8,8 +8,14 @@ class WgslOut { static var GLOBALS = { var m = new Map(); for( g in hxsl.Ast.TGlobal.createAll() ) { - var n = "" + g; - n = n.charAt(0).toLowerCase() + n.substr(1); + var n = switch( g ) { + case Mat4: "mat4x4"; + case Mat3: "mat3x3"; + case Mat2: "mat2x2"; + default: + var n = "" + g; + n = n.charAt(0).toLowerCase() + n.substr(1); + } m.set(g, n); } for( g in m ) @@ -136,10 +142,10 @@ class WgslOut { var name = "val" + (exprIds++); var tmp = buf; buf = new StringBuf(); - addType(e.t); - add(" "); + add("fn "); add(name); - add("(void)"); + add("() -> "); + addType(e.t); var el2 = el.copy(); var last = el2[el2.length - 1]; el2[el2.length - 1] = { e : TReturn(last), t : e.t, p : last.p }; @@ -246,8 +252,13 @@ class WgslOut { addValue(e,tabs); } add("}"); + case TBinop(OpMult,e1 = { t : TVec(3,_) },e2 = { t : TMat3x4 }): + add("vec4("); + addValue(e1,tabs); + add(",1) * "); + addValue(e2,tabs); case TBinop(op = OpAssign|OpAssignOp(_), { e : TSwiz(e,regs) }, e2) if( regs.length > 1 ): - // WSGL does not support swizzle writing outside of a single component (wth) + // WSGL does not support swizzle writing outside of a single component (wth) addValue(e, tabs); add(" = "); var size = switch(e.t) { @@ -310,9 +321,15 @@ class WgslOut { add("/*var*/"); } case TCall({ e : TGlobal(g) },args): + var name = GLOBALS.get(g); switch( [g,args] ) { case [Texture,[t,uv]]: - add("textureSample("); + if( t.t == TSampler2DArray ) { + decl("fn textureSampleArr( t : texture_2d_array, s : sampler, uv : vec3 ) -> vec4 { return textureSample(t,s,uv.xy,i32(round(uv.z))); } "); + add("textureSampleArr"); + } else + add("textureSample"); + add("("); addExpr(t, tabs); add(","); addExpr(t, tabs); @@ -320,16 +337,20 @@ class WgslOut { add(","); addExpr(uv, tabs); add(")"); + return; + case [Mat3, [m = { t : TMat4 }]]: + decl("fn mat4to3( m : mat4x4 ) -> mat3x3 { return mat3x3(m[0].xyz,m[1].xyz,m[2].xyz); }"); + name = "mat4to3"; default: - add(GLOBALS.get(g)); - add("("); - var first = true; - for( e in args ) { - if( first ) first = false else add(", "); - addValue(e, tabs); - } - add(")"); } + add(name); + add("("); + var first = true; + for( e in args ) { + if( first ) first = false else add(", "); + addValue(e, tabs); + } + add(")"); case TCall(e, args): addValue(e, tabs); add("(");