diff --git a/assets/UVEditorColorGrid.png b/assets/UVEditorColorGrid.png new file mode 100644 index 0000000..5fb3fa6 Binary files /dev/null and b/assets/UVEditorColorGrid.png differ diff --git a/src/geometry.ts b/src/geometry.ts new file mode 100644 index 0000000..4dff8bb --- /dev/null +++ b/src/geometry.ts @@ -0,0 +1,35 @@ +export class QuadGeometry { + public positions: number[]; + public colors: number[]; + public texCoords: number[]; + + constructor() { + this.positions = [ + -0.5, -0.5, + 0.5, -0.5, + -0.5, 0.5, + -0.5, 0.5, + 0.5, 0.5, + 0.5, -0.5 + ] + + this.colors = [ + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ] + + + this.texCoords = [ + 0.0, 1.0, + 1.0, 1.0, + 0.0, 0.0, + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0 + ] + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 4205086..cb2597b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,11 +1,16 @@ import shaderSource from './shaders/shader.wgsl?raw'; - +import { QuadGeometry } from './geometry'; +import { Texture } from './texture'; class Renderer{ private device! : GPUDevice; private context! : GPUCanvasContext; private pipeline! : GPURenderPipeline; private postitionBuffer! : GPUBuffer; private colorBuffer! : GPUBuffer; + private texCoordBuffer! : GPUBuffer; + private textureBindGroup! : GPUBindGroup; + + private testTexture! : Texture; public async initialize() { @@ -37,25 +42,18 @@ class Renderer{ format: navigator.gpu.getPreferredCanvasFormat() }) + + + this.testTexture = await Texture.createTextureFromUrl(this.device, 'assets/UVEditorColorGrid.png'); this.prepareModel(); - this.postitionBuffer = this.CreateBuffer(new Float32Array([ - -0.5, -0.5, - 0.5, -0.5, - -0.5, 0.5, - -0.5, 0.5, - 0.5, 0.5, - 0.5, -0.5 - ])); - this.colorBuffer = this.CreateBuffer(new Float32Array([ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - ])); + + const geometry = new QuadGeometry(); + + this.postitionBuffer = this.CreateBuffer(new Float32Array(geometry.positions)); + this.colorBuffer = this.CreateBuffer(new Float32Array(geometry.colors)); + this.texCoordBuffer = this.CreateBuffer(new Float32Array(geometry.texCoords)); } private CreateBuffer(data: Float32Array) @@ -102,12 +100,27 @@ class Renderer{ }] } + const textureCoordBufferLayout : GPUVertexBufferLayout = + { + arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, + attributes: [ + { + shaderLocation: 2, + offset: 0, + format: 'float32x2' + }], + stepMode: 'vertex' + } + const vertexState: GPUVertexState = { module : shaderModule, entryPoint: "VertexMain", buffers: - [postitionBufferLayout, - colorBufferLayout] + [ + postitionBufferLayout, + colorBufferLayout, + textureCoordBufferLayout + ] } const fragmentState: GPUFragmentState = { @@ -115,18 +128,65 @@ class Renderer{ entryPoint: "FragmentMain", targets: [ { - format: navigator.gpu.getPreferredCanvasFormat() + format: navigator.gpu.getPreferredCanvasFormat(), + blend: { + color: { + srcFactor: 'one', + dstFactor: 'zero', + operation: 'add' + }, + alpha: { + srcFactor: 'one', + dstFactor: 'zero', + operation: 'add' + } + } } ] } + const textureBindGroupLayout = this.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + sampler: {} + }, + { + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + texture: {} + } + ] + }); + + const pipelineLayout = this.device.createPipelineLayout({ + bindGroupLayouts: [ + textureBindGroupLayout + ] + }); + + this.textureBindGroup = this.device.createBindGroup({ + layout: textureBindGroupLayout, + entries: [ + { + binding: 0, + resource: this.testTexture.sampler + }, + { + binding: 1, + resource: this.testTexture.texture.createView() + } + ] + }); + this.pipeline = this.device.createRenderPipeline({ vertex: vertexState, fragment: fragmentState, primitive:{ topology: 'triangle-list', }, - layout: "auto" + layout: pipelineLayout }); @@ -154,6 +214,8 @@ class Renderer{ passEncoder.setPipeline(this.pipeline ); passEncoder.setVertexBuffer(0, this.postitionBuffer); passEncoder.setVertexBuffer(1, this.colorBuffer); + passEncoder.setVertexBuffer(2, this.texCoordBuffer); + passEncoder.setBindGroup(0, this.textureBindGroup) passEncoder.draw(6); passEncoder.end(); diff --git a/src/shaders/shader.wgsl b/src/shaders/shader.wgsl index 3be133e..6846118 100644 --- a/src/shaders/shader.wgsl +++ b/src/shaders/shader.wgsl @@ -1,24 +1,34 @@ struct VertexOut { @builtin(position) pos: vec4, @location(0) color: vec4, + @location(1) texcoord: vec2, } @vertex fn VertexMain( @location(0) pos: vec2, @location(1) color: vec3, + @location(2) texcoord: vec2, @builtin(vertex_index) vertexIndex: u32, ) -> VertexOut { var output : VertexOut; output.pos = vec4(pos, 0.0, 1.0); output.color = vec4(color, 1.0); + output.texcoord = vec2(texcoord); return output; } +@group(0) @binding(0) +var texSampler : sampler; + +@group(0) @binding(1) +var tex : texture_2d; + @fragment fn FragmentMain(fragData: VertexOut) -> @location(0) vec4 { - return fragData.color; + var texColor = textureSample(tex, texSampler, fragData.texcoord); + return fragData.color * texColor; } \ No newline at end of file diff --git a/src/texture.ts b/src/texture.ts new file mode 100644 index 0000000..08bc408 --- /dev/null +++ b/src/texture.ts @@ -0,0 +1,45 @@ +export class Texture{ + constructor(public texture: GPUTexture, public sampler: GPUSampler){} + + public static async createTexture(device: GPUDevice, image: HTMLImageElement) + : Promise + { + const texture = device.createTexture({ + size: { width: image.width, height: image.height}, + format: "rgba8unorm", + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT + }); + + const data = await createImageBitmap(image ); + + device.queue.copyExternalImageToTexture( + { source: data }, + { texture: texture }, + { width: image.width, height: image.height } + ); + + const sampler = device.createSampler({ + magFilter: "linear", + minFilter: "linear", + }); + + return new Texture(texture, sampler); + } + + public static async createTextureFromUrl(device: GPUDevice, url: string): Promise + { + const imagePromise = new Promise((resolve, reject) => { + const img = new Image(); + img.src = url; + img.onload = () => resolve(img); + img.onerror = () => { + console.error('Failed to load image from url: ' + url); + reject(); + } + }); + + const image = await imagePromise; + return await Texture.createTexture(device, image); + } + +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4feae2d..4006467 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, + "erasableSyntaxOnly": false, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true },