wip webgpu interfaces
This commit is contained in:
parent
5e6b9c8fb1
commit
9a54bb90c9
13 changed files with 235 additions and 118 deletions
|
@ -2,7 +2,7 @@ import { LoadOp, StoreOp } from '../enum.js'
|
||||||
import { CommandRecorderError } from '../utils/errors.js'
|
import { CommandRecorderError } from '../utils/errors.js'
|
||||||
import { SwapChain } from './swap-chain.js'
|
import { SwapChain } from './swap-chain.js'
|
||||||
|
|
||||||
export class CommandRecorder {
|
export class CommandEncoder implements GPUCommandEncoder {
|
||||||
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 }
|
static _defaultClearValue = { r: 0, g: 0, b: 0, a: 1 }
|
||||||
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
|
@ -29,24 +29,24 @@ export class CommandRecorder {
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
view,
|
view,
|
||||||
clearValue: CommandRecorder._defaultClearValue,
|
clearValue: CommandEncoder._defaultClearValue,
|
||||||
loadOp: LoadOp.Clear,
|
loadOp: LoadOp.Clear,
|
||||||
storeOp: StoreOp.Store
|
storeOp: StoreOp.Store
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
beginRenderPass(colorAttachments?: GPURenderPassColorAttachment[], depthStencilAttachment?: GPURenderPassDepthStencilAttachment): GPURenderPassEncoder {
|
beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder {
|
||||||
if (this._passEncoder) {
|
if (this._passEncoder) {
|
||||||
throw CommandRecorderError.activeRenderPass()
|
throw CommandRecorderError.activeRenderPass()
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachments = colorAttachments || this._defaultColorAttachment()
|
const attachments = colorAttachments || this._defaultColorAttachment()
|
||||||
|
|
||||||
const descriptor = {
|
//const descriptor = {
|
||||||
label: this.label || 'RenderPass',
|
// label: this.label || 'RenderPass',
|
||||||
colorAttachments: attachments,
|
// colorAttachments: attachments,
|
||||||
depthStencilAttachment
|
// depthStencilAttachment
|
||||||
}
|
//}
|
||||||
|
|
||||||
this._passEncoder = this._encoder.beginRenderPass(descriptor)
|
this._passEncoder = this._encoder.beginRenderPass(descriptor)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { SwapChain } from './swap-chain.js'
|
import { SwapChain } from './swap-chain.js'
|
||||||
import { Buffer, UniformBuffer } from '../resources/buffer.js'
|
import { Buffer, UniformBuffer } from '../resources/buffer.js'
|
||||||
import { GraphicsDeviceError, WebGPUError, BufferError, WebGPUObjectError } from '../utils/errors.js'
|
import { GraphicsDeviceError, WebGPUError, BufferError, WebGPUObjectError } from '../utils/errors.js'
|
||||||
import { GraphicsDeviceInitialized, GraphicsDeviceLost } from '../utils/events.js'
|
import { EventEmitter, GraphicsDeviceInitialized, GraphicsDeviceLost } from '../utils/events.js'
|
||||||
import { EventEmitter } from '../utils/index.js'
|
|
||||||
import { ShaderModule, ShaderPairDescriptor } from '../resources/shader-module.js'
|
import { ShaderModule, ShaderPairDescriptor } from '../resources/shader-module.js'
|
||||||
import { RenderPipeline } from '../rendering/render-pipeline.js'
|
import { RenderPipeline } from '../rendering/render-pipeline.js'
|
||||||
import { CommandRecorder } from './command-recorder.js'
|
import { CommandEncoder } from './command-recorder.js'
|
||||||
import { BindGroupLayout } from '../resources/bind-group-layout.js'
|
import { BindGroupLayout } from '../resources/bind-group-layout.js'
|
||||||
import { BindGroup } from '../resources/bind-group.js'
|
import { BindGroup } from '../resources/bind-group.js'
|
||||||
import { Texture } from '../resources/texture.js'
|
import { Texture } from '../resources/texture.js'
|
||||||
|
@ -125,7 +124,9 @@ class DeviceHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphicsDevice extends EventEmitter {
|
export class GraphicsDevice extends EventEmitter implements GPUDevice {
|
||||||
|
__brand: 'GPUDevice'
|
||||||
|
|
||||||
_canvas: HTMLCanvasElement
|
_canvas: HTMLCanvasElement
|
||||||
_deviceHandler: DeviceHandler
|
_deviceHandler: DeviceHandler
|
||||||
_swapChain: SwapChain
|
_swapChain: SwapChain
|
||||||
|
@ -153,6 +154,34 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
return this._swapChain
|
return this._swapChain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get features() {
|
||||||
|
return this.device.features
|
||||||
|
}
|
||||||
|
|
||||||
|
get limits() {
|
||||||
|
return this.device.limits
|
||||||
|
}
|
||||||
|
|
||||||
|
get adapterInfo() {
|
||||||
|
return this.device.adapterInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
get lost() {
|
||||||
|
return this.device.lost
|
||||||
|
}
|
||||||
|
|
||||||
|
get onuncapturederror() {
|
||||||
|
return this.device.onuncapturederror
|
||||||
|
}
|
||||||
|
|
||||||
|
set onuncapturederror(value) {
|
||||||
|
this.device.onuncapturederror = value
|
||||||
|
}
|
||||||
|
|
||||||
|
get label() {
|
||||||
|
return this.device.label
|
||||||
|
}
|
||||||
|
|
||||||
constructor(canvas: HTMLCanvasElement, deviceHandler: DeviceHandler) {
|
constructor(canvas: HTMLCanvasElement, deviceHandler: DeviceHandler) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
|
@ -160,6 +189,34 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
this._deviceHandler = deviceHandler
|
this._deviceHandler = deviceHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importExternalTexture(descriptor: GPUExternalTextureDescriptor): GPUExternalTexture {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createComputePipeline(descriptor: GPUComputePipelineDescriptor): GPUComputePipeline {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createComputePipelineAsync(descriptor: GPUComputePipelineDescriptor): Promise<GPUComputePipeline> {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createRenderPipelineAsync(descriptor: GPURenderPipelineDescriptor): Promise<GPURenderPipeline> {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createCommandEncoder(descriptor?: GPUCommandEncoderDescriptor): GPUCommandEncoder {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createRenderBundleEncoder(descriptor: GPURenderBundleEncoderDescriptor): GPURenderBundleEncoder {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
pushErrorScope(filter: GPUErrorFilter): undefined {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
popErrorScope(): Promise<GPUError | null> {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
static build(canvas?: HTMLCanvasElement) {
|
static build(canvas?: HTMLCanvasElement) {
|
||||||
return new GraphicsDeviceBuilder(canvas)
|
return new GraphicsDeviceBuilder(canvas)
|
||||||
}
|
}
|
||||||
|
@ -226,11 +283,11 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
createShaderModule(code: string, label: string): ShaderModule {
|
createShaderModule(descriptor: GPUShaderModuleDescriptor): ShaderModule {
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ShaderModule.create(this.device, { code, label })
|
return ShaderModule.create(this.device, descriptor)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw WebGPUObjectError.from(err, ShaderModule)
|
throw WebGPUObjectError.from(err, ShaderModule)
|
||||||
}
|
}
|
||||||
|
@ -257,19 +314,16 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createCommandRecorder(label?: string): CommandRecorder {
|
createCommandRecorder(label?: string): CommandEncoder {
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
return new CommandRecorder(this.device, this._swapChain, label)
|
return new CommandEncoder(this.device, this._swapChain, label)
|
||||||
}
|
}
|
||||||
|
|
||||||
createBindGroupLayout(entries: GPUBindGroupLayoutEntry[], label: string) {
|
createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor) {
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
return BindGroupLayout.create(this.device, {
|
return BindGroupLayout.create(this.device, descriptor)
|
||||||
label,
|
|
||||||
entries
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_getBindingResource(binding: BindGroupEntry): GPUBindingResource {
|
_getBindingResource(binding: BindGroupEntry): GPUBindingResource {
|
||||||
|
@ -303,31 +357,24 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createBindGroup(layout: BindGroupLayout, bindings: BindGroupEntry[], label?: string) {
|
createBindGroup(descriptor: GPUBindGroupDescriptor) {
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
|
|
||||||
const entries = bindings.map(def => ({
|
//const entries = bindings.map(def => ({
|
||||||
binding: def.binding,
|
// binding: def.binding,
|
||||||
resource: this._getBindingResource(def)
|
// resource: this._getBindingResource(def)
|
||||||
}))
|
//}))
|
||||||
|
|
||||||
return BindGroup.create(this.device, {
|
return BindGroup.create(this.device, descriptor)
|
||||||
layout: layout.handle,
|
|
||||||
entries,
|
|
||||||
label
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createPipelineLayout(layouts: Array<BindGroupLayout>, label?: string) {
|
createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor) {
|
||||||
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
if (!this._isInitialized) { throw GraphicsDeviceError.uninitialized() }
|
||||||
|
|
||||||
const bindGroupLayouts = layouts.map(layout => layout.handle)
|
//const bindGroupLayouts = layouts.map(layout => layout.handle)
|
||||||
|
|
||||||
return this.device.createPipelineLayout({
|
return this.device.createPipelineLayout(descriptor)
|
||||||
label,
|
|
||||||
bindGroupLayouts
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSampler(descriptor?: GPUSamplerDescriptor) {
|
createSampler(descriptor?: GPUSamplerDescriptor) {
|
||||||
|
@ -389,10 +436,7 @@ export class GraphicsDevice extends EventEmitter {
|
||||||
this.queue.submit(commandBuffers)
|
this.queue.submit(commandBuffers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
destroy(): undefined {
|
||||||
* Cleans up GPU resources. Call when the application exits.
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
if (this.device) {
|
if (this.device) {
|
||||||
this.device.destroy()
|
this.device.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
export class RenderPipeline {
|
export class RenderPipeline implements GPURenderPipeline {
|
||||||
|
__brand: 'GPURenderPipeline'
|
||||||
|
|
||||||
_handle: GPURenderPipeline
|
_handle: GPURenderPipeline
|
||||||
_label: string
|
_label: string
|
||||||
|
|
||||||
|
@ -14,5 +16,9 @@ export class RenderPipeline {
|
||||||
this._handle = pipeline
|
this._handle = pipeline
|
||||||
this._label = label
|
this._label = label
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBindGroupLayout(index: number): GPUBindGroupLayout {
|
||||||
|
return this.getBindGroupLayout(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { WebGPUObjectError } from '../utils/errors.js'
|
import { WebGPUObjectError } from '../utils/errors.js'
|
||||||
|
|
||||||
export class BindGroupLayout {
|
export class BindGroupLayout implements GPUBindGroupLayout {
|
||||||
|
__brand: 'GPUBindGroupLayout'
|
||||||
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
_handle: GPUBindGroupLayout
|
_handle: GPUBindGroupLayout
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { WebGPUObjectError } from '../utils/errors.js'
|
import { WebGPUObjectError } from '../utils/errors.js'
|
||||||
|
|
||||||
export class BindGroup {
|
export class BindGroup implements GPUBindGroup {
|
||||||
|
__brand: 'GPUBindGroup'
|
||||||
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
_handle: GPUBindGroup
|
_handle: GPUBindGroup
|
||||||
|
|
||||||
|
@ -8,6 +10,10 @@ export class BindGroup {
|
||||||
return this._handle
|
return this._handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get label() {
|
||||||
|
return this._handle.label
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {GPUDevice} device
|
* @param {GPUDevice} device
|
||||||
* @param {GPUBindGroup} bindGroup
|
* @param {GPUBindGroup} bindGroup
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { ResourceType } from '../utils/internal-enums.js'
|
||||||
type SmallTypedArray = Exclude<TypedArray, BigInt64Array | BigUint64Array>
|
type SmallTypedArray = Exclude<TypedArray, BigInt64Array | BigUint64Array>
|
||||||
type SmallTypedArrayConstructor = Exclude<TypedArrayConstructor, BigInt64ArrayConstructor | BigUint64ArrayConstructor>
|
type SmallTypedArrayConstructor = Exclude<TypedArrayConstructor, BigInt64ArrayConstructor | BigUint64ArrayConstructor>
|
||||||
|
|
||||||
export class Buffer {
|
export class Buffer implements GPUBuffer {
|
||||||
|
__brand: 'GPUBuffer'
|
||||||
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
_handle: GPUBuffer
|
_handle: GPUBuffer
|
||||||
|
|
||||||
|
@ -25,6 +27,14 @@ export class Buffer {
|
||||||
return this._handle.usage
|
return this._handle.usage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get mapState() {
|
||||||
|
return this._handle.mapState
|
||||||
|
}
|
||||||
|
|
||||||
|
get label() {
|
||||||
|
return this._handle.label
|
||||||
|
}
|
||||||
|
|
||||||
get resourceType() {
|
get resourceType() {
|
||||||
return ResourceType.Buffer
|
return ResourceType.Buffer
|
||||||
}
|
}
|
||||||
|
@ -33,7 +43,6 @@ export class Buffer {
|
||||||
this._device = device
|
this._device = device
|
||||||
this._handle = texture
|
this._handle = texture
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(device: GPUDevice, descriptor: GPUBufferDescriptor) {
|
static create(device: GPUDevice, descriptor: GPUBufferDescriptor) {
|
||||||
try {
|
try {
|
||||||
return new Buffer(
|
return new Buffer(
|
||||||
|
@ -153,7 +162,20 @@ export class Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
mapAsync(mode: GPUMapModeFlags, offset?: GPUSize64, size?: GPUSize64): Promise<undefined> {
|
||||||
|
return this._handle.mapAsync(mode, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMappedRange(offset?: GPUSize64, size?: GPUSize64): ArrayBuffer {
|
||||||
|
return this._handle.getMappedRange(offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
unmap(): undefined {
|
||||||
|
this._handle.unmap()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
destroy(): undefined {
|
||||||
this._handle?.destroy()
|
this._handle?.destroy()
|
||||||
this._handle = null
|
this._handle = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { WebGPUObjectError } from '../utils/errors.js'
|
import { WebGPUObjectError } from '../utils/errors.js'
|
||||||
import { ResourceType } from '../utils/internal-enums.js'
|
import { ResourceType } from '../utils/internal-enums.js'
|
||||||
|
|
||||||
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
export class Sampler implements GPUSampler {
|
||||||
|
__brand: 'GPUSampler'
|
||||||
|
|
||||||
export class Sampler {
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
_handle: GPUSampler
|
_handle: GPUSampler
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,8 @@ import {
|
||||||
} from '../utils/wgsl-to-wgpu.js'
|
} from '../utils/wgsl-to-wgpu.js'
|
||||||
import { BufferBindingType } from '../enum.js'
|
import { BufferBindingType } from '../enum.js'
|
||||||
|
|
||||||
/** @import { WGSLAccess, WGSLSamplerType } from '../utils/wgsl-to-wgpu.js' */
|
export class ShaderModule implements GPUShaderModule {
|
||||||
|
__brand: 'GPUShaderModule'
|
||||||
export class ShaderModule {
|
|
||||||
_handle: GPUShaderModule
|
_handle: GPUShaderModule
|
||||||
_code: string
|
_code: string
|
||||||
|
|
||||||
|
@ -44,14 +43,17 @@ export class ShaderModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
reflect() {
|
reflect() {
|
||||||
if (this._reflection == null) {
|
if (!this._reflection) {
|
||||||
this._reflection = new WgslReflect(this._code)
|
this._reflection = new WgslReflect(this._code)
|
||||||
// no longer needed allow the GC to collect it
|
|
||||||
this._code = undefined
|
this._code = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._reflection
|
return this._reflection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCompilationInfo(): Promise<GPUCompilationInfo> {
|
||||||
|
return this._handle.getCompilationInfo()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FragmentStateDescriptor {
|
export interface FragmentStateDescriptor {
|
||||||
|
@ -83,7 +85,7 @@ export class ReflectedShader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(shader: ShaderModule) {
|
constructor(shader: ShaderModule, reflection: WgslReflect) {
|
||||||
this._module = shader
|
this._module = shader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,19 @@ import { WebGPUObjectError } from '../utils/errors.js'
|
||||||
import { ResourceType } from '../utils/internal-enums.js'
|
import { ResourceType } from '../utils/internal-enums.js'
|
||||||
import { textureToImageDimension } from '../utils/wgsl-to-wgpu.js'
|
import { textureToImageDimension } from '../utils/wgsl-to-wgpu.js'
|
||||||
|
|
||||||
/** @import { BindGroupEntry } from '../core/graphics-device.js' */
|
|
||||||
interface UploadTextureInfo {
|
interface UploadTextureInfo {
|
||||||
destination: GPUTexelCopyTextureInfo
|
destination: GPUTexelCopyTextureInfo
|
||||||
dataLayout: GPUTexelCopyBufferLayout
|
dataLayout: GPUTexelCopyBufferLayout
|
||||||
size: GPUExtent3DStrict
|
size: GPUExtent3DStrict
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Texture {
|
export class Texture implements GPUTexture {
|
||||||
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING
|
static _defaultUsage = GPUTextureUsage.TEXTURE_BINDING
|
||||||
| GPUTextureUsage.COPY_DST
|
| GPUTextureUsage.COPY_DST
|
||||||
| GPUTextureUsage.RENDER_ATTACHMENT
|
| GPUTextureUsage.RENDER_ATTACHMENT
|
||||||
|
|
||||||
|
__brand: 'GPUTexture'
|
||||||
|
|
||||||
_device: GPUDevice
|
_device: GPUDevice
|
||||||
_handle: GPUTexture
|
_handle: GPUTexture
|
||||||
|
|
||||||
|
@ -48,6 +49,10 @@ export class Texture {
|
||||||
return this._handle.dimension
|
return this._handle.dimension
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sampleCount() {
|
||||||
|
return this._handle.sampleCount
|
||||||
|
}
|
||||||
|
|
||||||
get mipLevelCount() {
|
get mipLevelCount() {
|
||||||
return this._handle.mipLevelCount
|
return this._handle.mipLevelCount
|
||||||
}
|
}
|
||||||
|
@ -81,7 +86,6 @@ export class Texture {
|
||||||
return 1 + Math.log2(max) | 0
|
return 1 + Math.log2(max) | 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static async fromUrl(device: GPUDevice, url: string | URL, descriptor: GPUTextureDescriptor) {
|
static async fromUrl(device: GPUDevice, url: string | URL, descriptor: GPUTextureDescriptor) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
|
@ -127,7 +131,6 @@ export class Texture {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
upload(source: GPUAllowSharedBufferSource, options?: UploadTextureInfo) {
|
upload(source: GPUAllowSharedBufferSource, options?: UploadTextureInfo) {
|
||||||
const mipLevel = options.destination.mipLevel || 0
|
const mipLevel = options.destination.mipLevel || 0
|
||||||
const size = options.size || [
|
const size = options.size || [
|
||||||
|
@ -148,8 +151,8 @@ export class Texture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
generateMipmaps(_commandEncoder: GPUCommandEncoder) {
|
createView(descriptor?: GPUTextureViewDescriptor) {
|
||||||
// TODO: use MipGenerator
|
return this._handle.createView(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
createDefaultView(descriptor?: GPUTextureViewDescriptor) {
|
createDefaultView(descriptor?: GPUTextureViewDescriptor) {
|
||||||
|
@ -178,7 +181,7 @@ export class Texture {
|
||||||
return this.getView()
|
return this.getView()
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy(): undefined {
|
||||||
this._handle?.destroy()
|
this._handle?.destroy()
|
||||||
this._handle = undefined
|
this._handle = undefined
|
||||||
this._defaultView = undefined
|
this._defaultView = undefined
|
||||||
|
|
|
@ -1,5 +1,54 @@
|
||||||
import { GraphicsDevice } from '../core/graphics-device.js'
|
import { GraphicsDevice } from '../core/graphics-device.js'
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
(...args: any): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventEmitter {
|
||||||
|
_listeners = {}
|
||||||
|
|
||||||
|
addEventListener(type: PropertyKey, listener: Listener, options?: object): void {
|
||||||
|
this.on(type, listener, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener(type: PropertyKey, listener: Listener, options?: object): void {
|
||||||
|
this.off(type, listener, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(event: Event): boolean {
|
||||||
|
const details = 'detail' in event ? event.detail : undefined
|
||||||
|
return this.emit(event.type, ...Object.values(details))
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event: PropertyKey, callback: Listener, _options?: object) {
|
||||||
|
this._listeners[event] = this._listeners[event] || []
|
||||||
|
this._listeners[event].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(event: PropertyKey, ...args: any[]) {
|
||||||
|
const listeners = this._listeners[event]
|
||||||
|
|
||||||
|
if (listeners) {
|
||||||
|
listeners.forEach(
|
||||||
|
(cb: Listener) => cb(...args)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
off(event: PropertyKey, callback: Listener, _options?: object) {
|
||||||
|
const listeners = this._listeners[event]
|
||||||
|
|
||||||
|
if (listeners) {
|
||||||
|
this._listeners[event] = listeners.filter(
|
||||||
|
(cb: Listener) => cb !== callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class GraphicsDeviceInitialized {
|
export class GraphicsDeviceInitialized {
|
||||||
static EventName = 'graphics-device:initialized'
|
static EventName = 'graphics-device:initialized'
|
||||||
graphicsDevice: GraphicsDevice
|
graphicsDevice: GraphicsDevice
|
||||||
|
|
|
@ -88,38 +88,6 @@ export const FlagEnum = <const T extends string, const A extends T[]>(...values:
|
||||||
) as FlagEnum<T, A>
|
) as FlagEnum<T, A>
|
||||||
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
(...args: any): void
|
|
||||||
}
|
|
||||||
export class EventEmitter {
|
|
||||||
_listeners = {}
|
|
||||||
|
|
||||||
on(event: PropertyKey, callback: Listener) {
|
|
||||||
this._listeners[event] = this._listeners[event] || []
|
|
||||||
this._listeners[event].push(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: PropertyKey, ...args: any[]) {
|
|
||||||
const listeners = this._listeners[event]
|
|
||||||
|
|
||||||
if (listeners) {
|
|
||||||
listeners.forEach(
|
|
||||||
(cb: Listener) => cb(...args)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
off(event: PropertyKey, callback: Listener) {
|
|
||||||
const listeners = this._listeners[event]
|
|
||||||
|
|
||||||
if (listeners) {
|
|
||||||
this._listeners[event] = listeners.filter(
|
|
||||||
(cb: Listener) => cb !== callback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pick = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> => (Object.fromEntries(
|
export const pick = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> => (Object.fromEntries(
|
||||||
keys
|
keys
|
||||||
.filter(key => key in obj)
|
.filter(key => key in obj)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// taken from PixiJS
|
||||||
|
// https://pixijs.download/v8.0.0-rc.2/docs/rendering_renderers_gpu_texture_utils_GpuMipmapGenerator.ts.html
|
||||||
|
|
||||||
import code from './mip-shader.wgsl'
|
import code from './mip-shader.wgsl'
|
||||||
|
|
||||||
const mip = (n: number) => Math.max(1, n >>> 1)
|
const mip = (n: number) => Math.max(1, n >>> 1)
|
||||||
|
@ -81,33 +84,37 @@ export class MipGenerator {
|
||||||
return pipeline
|
return pipeline
|
||||||
}
|
}
|
||||||
|
|
||||||
generateMipmap(texture: GPUTexture, descriptor: GPUTextureDescriptor) {
|
generateMipmap(texture: GPUTexture, commandEncoder?: GPUCommandEncoder): GPUTexture {
|
||||||
const pipeline = this.getPipeline(descriptor.format)
|
const pipeline = this.getPipeline(texture.format)
|
||||||
|
|
||||||
if (descriptor.dimension !== '2d') {
|
const { dimension, format, mipLevelCount, width, height, depthOrArrayLayers } = texture
|
||||||
|
|
||||||
|
const encoder = commandEncoder || this._device.createCommandEncoder()
|
||||||
|
|
||||||
|
if (texture.dimension !== '2d') {
|
||||||
throw new Error('Generating mipmaps for anything except 2d is unsupported.')
|
throw new Error('Generating mipmaps for anything except 2d is unsupported.')
|
||||||
}
|
}
|
||||||
|
|
||||||
let mipTexture = texture
|
let tempTexture = texture
|
||||||
const { width, height, depthOrArrayLayers } = descriptor.size as GPUExtent3DDict
|
|
||||||
|
|
||||||
const renderToSource = descriptor.usage & GPUTextureUsage.RENDER_ATTACHMENT
|
|
||||||
|
const renderToSource = texture.usage & GPUTextureUsage.RENDER_ATTACHMENT
|
||||||
|
|
||||||
if (!renderToSource) {
|
if (!renderToSource) {
|
||||||
mipTexture = this._device.createTexture({
|
tempTexture = this._device.createTexture({
|
||||||
size: {
|
size: {
|
||||||
width: mip(width),
|
width: mip(width),
|
||||||
height: mip(height),
|
height: mip(height),
|
||||||
depthOrArrayLayers
|
depthOrArrayLayers
|
||||||
},
|
},
|
||||||
format: descriptor.format,
|
format,
|
||||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
|
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||||
mipLevelCount: descriptor.mipLevelCount - 1
|
mipLevelCount: mipLevelCount - 1,
|
||||||
|
dimension,
|
||||||
|
sampleCount: 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const encoder = this._device.createCommandEncoder({})
|
|
||||||
|
|
||||||
for (let layer = 0; layer < depthOrArrayLayers; ++layer) {
|
for (let layer = 0; layer < depthOrArrayLayers; ++layer) {
|
||||||
let srcView = texture.createView({
|
let srcView = texture.createView({
|
||||||
baseMipLevel: 0,
|
baseMipLevel: 0,
|
||||||
|
@ -117,10 +124,9 @@ export class MipGenerator {
|
||||||
arrayLayerCount: 1
|
arrayLayerCount: 1
|
||||||
})
|
})
|
||||||
|
|
||||||
let dstMipLevel = renderToSource ? 1 : 0
|
for (let i = 1; i < mipLevelCount; ++i) {
|
||||||
for (let i = 1; i < descriptor.mipLevelCount; ++i) {
|
const dstView = tempTexture.createView({
|
||||||
const dstView = mipTexture.createView({
|
baseMipLevel: i - (renderToSource ? 0 : 1),
|
||||||
baseMipLevel: dstMipLevel++,
|
|
||||||
mipLevelCount: 1,
|
mipLevelCount: 1,
|
||||||
dimension: '2d',
|
dimension: '2d',
|
||||||
baseArrayLayer: layer,
|
baseArrayLayer: layer,
|
||||||
|
@ -156,33 +162,42 @@ export class MipGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!renderToSource) {
|
if (!renderToSource) {
|
||||||
const mipLevelSize = {
|
const mipSize = {
|
||||||
width: mip(width),
|
width: mip(width),
|
||||||
height: mip(height),
|
height: mip(height),
|
||||||
depthOrArrayLayers
|
depthOrArrayLayers
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 1; i < descriptor.mipLevelCount; ++i) {
|
for (let i = 1; i < texture.mipLevelCount; ++i) {
|
||||||
encoder.copyTextureToTexture({
|
encoder.copyTextureToTexture({
|
||||||
texture: mipTexture,
|
texture: tempTexture,
|
||||||
mipLevel: i - 1
|
mipLevel: i - 1
|
||||||
}, {
|
}, {
|
||||||
texture,
|
texture,
|
||||||
mipLevel: i,
|
mipLevel: i,
|
||||||
}, mipLevelSize)
|
}, mipSize)
|
||||||
|
|
||||||
mipLevelSize.width = mip(mipLevelSize.width)
|
mipSize.width = mip(mipSize.width)
|
||||||
mipLevelSize.height = mip(mipLevelSize.height)
|
mipSize.height = mip(mipSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._device.queue.submit([encoder.finish()])
|
this._device.queue.submit([encoder.finish()])
|
||||||
|
|
||||||
if (!renderToSource) {
|
if (!renderToSource) {
|
||||||
mipTexture.destroy()
|
tempTexture.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
return texture
|
return texture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this._device = undefined
|
||||||
|
this._sampler = undefined
|
||||||
|
this._shader = undefined
|
||||||
|
this._pipelines = undefined
|
||||||
|
this._bindGroupLayout = undefined
|
||||||
|
this._pipelineLayout = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ struct VertexOutput {
|
||||||
};
|
};
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
|
fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
|
||||||
var output : VertexOutput;
|
var output : VertexOutput;
|
||||||
output.texCoord = pos[vertexIndex] * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
|
output.texCoord = pos[vertexIndex] * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5);
|
||||||
output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
output.position = vec4<f32>(pos[vertexIndex], 0.0, 1.0);
|
||||||
|
@ -18,6 +18,6 @@ fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
|
||||||
@group(0) @binding(1) var img : texture_2d<f32>;
|
@group(0) @binding(1) var img : texture_2d<f32>;
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fragmentMain(@location(0) texCoord : vec2<f32>) -> @location(0) vec4<f32> {
|
fn fs_main(@location(0) texCoord : vec2<f32>) -> @location(0) vec4<f32> {
|
||||||
return textureSample(img, imgSampler, texCoord);
|
return textureSample(img, imgSampler, texCoord);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue