xgpu
Version:
XGPU is an extendable library for WebGPU that provides a higher-level, easy-to-use interface for building rendering engines or processing numeric data. It handles automatic data binding, buffer alignment, variable declarations in shaders, and more. XGPU i
276 lines (275 loc) • 11.7 kB
JavaScript
// Copyright (c) 2023 Thomas Le Coz. All rights reserved.
// This code is governed by an MIT license that can be found in the LICENSE file.
import { EventDispatcher } from "../../EventDispatcher";
import { XGPU } from "../../XGPU";
export class ImageTexture extends EventDispatcher {
resourceIO;
io = 0;
mustBeTransfered = false;
descriptor;
gpuResource;
_view;
viewDescriptor = undefined;
useOutsideTexture = false;
renderPassTexture;
constructor(descriptor) {
super();
descriptor = { ...descriptor };
this.descriptor = descriptor;
//console.warn("imageTExture descriptor = ", descriptor);
if (undefined === descriptor.sampledType)
descriptor.sampledType = "f32";
if (undefined === descriptor.usage)
descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT;
if (undefined === descriptor.format)
descriptor.format = "rgba8unorm";
if (undefined === descriptor.label)
descriptor.label = "ImageTexture";
if (undefined === descriptor.size) {
if (descriptor.source) {
descriptor.size = [descriptor.source.width, descriptor.source.height];
if (descriptor.source instanceof GPUTexture) {
this.initFromTexture(descriptor.source);
}
else if (descriptor.source instanceof ImageTexture && descriptor.source.isRenderPass) {
//I canoot use instanceof in the constructor of ImageTexture with a class that extends it.
//but I wanted to write 'if (descriptor.source instanceof RenderPassTexture){'
this.renderPassTexture = descriptor.source;
//console.log("wait resource changed")
this.renderPassTexture.addEventListener("RESOURCE_CHANGED", () => {
//console.log("ON_RESOURCE_CHANGED")
this.initFromTexture(this.renderPassTexture.texture);
});
this.initFromTexture(descriptor.source.texture);
}
}
else
descriptor.size = [1, 1];
}
if (descriptor.source)
this.mustBeTransfered = true;
//this.createGpuResource()
}
initFromTexture(tex) {
this.gpuResource = tex;
this.descriptor.format = tex.format;
this.descriptor.usage = tex.usage;
this._view = this.gpuResource.createView();
this.descriptor.source = undefined;
this.useOutsideTexture = true;
}
clone() {
return new ImageTexture(this.descriptor);
}
get sampledType() { return this.descriptor.sampledType; }
set sampledType(type) { this.descriptor.sampledType = type; }
get isRenderPass() { return false; }
gpuTextureIOs;
gpuTextureIO_index = 1;
initTextureIO(textures) {
this.gpuTextureIOs = textures;
}
get texture() {
//if (this.gpuTextureIOs) return this.gpuTextureIOs[this.gpuTextureIO_index++ % 2]
return this.gpuResource;
}
getCurrentTexture() {
//if (this.gpuTextureIOs) return this.gpuTextureIOs[(this.gpuTextureIO_index + 1) % 2]
return this.gpuResource;
}
createView(viewDescriptor) {
if (this.useOutsideTexture)
return null;
let desc = this.viewDescriptor;
if (viewDescriptor)
desc = viewDescriptor;
return this.gpuResource.createView(desc);
}
resize(w, h) {
if (this.useOutsideTexture)
return null;
this.descriptor.size = [w, h];
this.createGpuResource();
return this;
}
get view() {
//console.log("aaa")
if (!this._view)
this.createGpuResource();
return this._view;
}
get source() { return this.descriptor.source; }
set source(bmp) {
this.useOutsideTexture = bmp instanceof GPUTexture || (bmp instanceof ImageTexture && bmp.isRenderPass);
//console.warn("SOURCE ============================================== source = ", bmp)
if (this.useOutsideTexture) {
if (bmp instanceof GPUTexture) {
this.gpuResource = bmp;
this._view = this.gpuResource.createView();
}
else {
this.renderPassTexture = bmp;
this.renderPassTexture.clearEvents("RESOURCE_CHANGED");
this.renderPassTexture.addEventListener("RESOURCE_CHANGED", () => {
//console.log("ON_RESOURCE_CHANGED")
this.initFromTexture(this.renderPassTexture.texture);
});
this.gpuResource = bmp.texture;
this._view = this.gpuResource.createView();
}
}
else
this.mustBeTransfered = true;
this.descriptor.source = bmp;
}
update(pipeline) {
if (this.renderPassTexture && !this.renderPassTexture.mustUseCopyTextureToTexture)
this.renderPassTexture.applyRenderPass(pipeline);
if (this.useOutsideTexture)
return;
if (!this.gpuResource)
this.createGpuResource();
if (this.descriptor.source) {
if (this.descriptor.source.width !== this.gpuResource.width || this.descriptor.source.height !== this.gpuResource.height) {
//console.log("source = ", this.descriptor.source)
this.descriptor.size = [this.descriptor.source.width, this.descriptor.source.height];
this.createGpuResource();
this.mustBeTransfered = true;
}
}
if (this.mustBeTransfered) {
this.mustBeTransfered = false;
//console.log("updateTexture")
XGPU.device.queue.copyExternalImageToTexture({ source: this.descriptor.source, flipY: true }, { texture: this.gpuResource }, this.descriptor.size);
}
}
deviceId;
createGpuResource() {
//console.warn("imageTexture.createGpuResource ", this.deviceId, XGPU.deviceId, this.useOutsideTexture, this.descriptor.source)
if (this.useOutsideTexture && this.gpuResource) {
if (this.deviceId != XGPU.deviceId) {
const o = this.gpuResource.xgpuObject;
if (o) {
o.createGpuResource();
//console.log("o = ", o)
this.gpuResource = o.gpuResource;
this._view = o.view;
}
}
}
this.deviceId = XGPU.deviceId;
if (this.useOutsideTexture || this.gpuTextureIOs)
return;
if (this.gpuResource) {
this.gpuResource.xgpuObject = null;
this.gpuResource.destroy();
}
this.gpuResource = XGPU.device.createTexture(this.descriptor);
this.gpuResource.xgpuObject = this;
this._view = this.gpuResource.createView();
if (this.descriptor.source)
this.mustBeTransfered = true;
}
time;
destroyGpuResource() {
if (this.time && new Date().getTime() - this.time < 100 && XGPU.loseDeviceRecently) {
//this.createGpuResource()
return;
}
this.time = new Date().getTime();
if (this.io && XGPU.loseDeviceRecently) {
if (this.io === 1) {
const vbio = this.resourceIO;
const vbs = vbio.textures;
let temp = vbs[0].gpuTextureIOs;
vbs[0].gpuTextureIOs = null;
vbs[0].createGpuResource();
vbs[0].gpuTextureIOs = temp;
temp = vbs[1].gpuTextureIOs;
vbs[1].gpuTextureIOs = null;
vbs[1].createGpuResource();
vbs[1].gpuTextureIOs = temp;
vbs[0].gpuTextureIOs[0] = vbs[0].gpuResource;
vbs[0].gpuTextureIOs[1] = vbs[1].gpuResource;
}
return;
}
if (this.resourceIO)
this.resourceIO.destroy();
if (this.useOutsideTexture || this.gpuTextureIOs)
return;
if (this.gpuResource) {
this.gpuResource.xgpuObject = null;
this.gpuResource.destroy();
}
this._view = null;
this.gpuResource = null;
this.resourceIO = null;
}
createDeclaration(varName, bindingId, groupId = 0) {
if (this.io != 2)
return "@binding(" + bindingId + ") @group(" + groupId + ") var " + varName + ":texture_2d<" + this.sampledType + ">;\n";
return " @binding(" + (bindingId) + ") @group(" + groupId + ") var " + varName + " : texture_storage_2d<rgba8unorm, write>;\n";
}
_textureType;
get textureType() { return this._textureType; }
set textureType(o) {
//console.log("set textureType ", o)
this._textureType = o;
}
createBindGroupLayoutEntry(bindingId) {
let sampleType = "float";
if (this.sampledType === "i32")
sampleType = "sint";
else if (this.sampledType === "u32")
sampleType = "uint";
//console.warn("createBindGroupLayoutEntry ", this.io)
if (this.io != 2)
return {
binding: bindingId,
visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
...this.textureType,
texture: {
sampleType,
viewDimension: "2d",
multisampled: false
},
};
return {
binding: bindingId,
visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
storageTexture: {
access: "write-only",
format: "rgba8unorm"
},
};
}
createBindGroupEntry(bindingId) {
if (!this.gpuResource || this.deviceId != XGPU.deviceId)
this.createGpuResource();
//console.log("ImageTexture.createBindgroupEntry ", this.deviceId)
return {
binding: bindingId,
resource: this._view
};
}
setPipelineType(pipelineType) {
//use to handle particular cases in descriptor relative to the nature of pipeline
if (pipelineType === "render") {
this.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT;
}
else if (pipelineType === "compute_mixed") { //the image is processed from a ComputePipeline and use inside a RenderPipeline
if (this.io === 1) { //read buffer
this.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST;
}
else if (this.io === 2) { //write buffer
this.descriptor.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING;
}
}
else if (pipelineType === "compute") {
if (this.io !== 0) {
this.descriptor.usage = GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING;
}
}
}
}