diff --git a/dev-demos/webgpu/device/demos/texture.tsx b/dev-demos/webgpu/device/demos/texture.tsx index 91da455340..b92736e4f7 100644 --- a/dev-demos/webgpu/device/demos/texture.tsx +++ b/dev-demos/webgpu/device/demos/texture.tsx @@ -51,7 +51,8 @@ export default () => { ` }, fragment: { - wgsl: ` + wgsl: + ` [[location(0)]] var v_uv : vec2; [[location(0)]] var outputColor : vec4; diff --git a/examples/demos/raster/dem.ts b/examples/demos/raster/dem.ts new file mode 100644 index 0000000000..65e5519428 --- /dev/null +++ b/examples/demos/raster/dem.ts @@ -0,0 +1,62 @@ +import { Scene, RasterLayer } from '@antv/l7'; +import * as allMap from '@antv/l7-maps'; +import * as GeoTIFF from 'geotiff'; + +export function MapRender(option: { + map: string + renderer: string +}) { + + const scene = new Scene({ + id: 'map', + // renderer: option.renderer, + map: new allMap[option.map || 'Map']({ + style: 'light', + center: [121.434765, 31.256735], + zoom: 3 + }) + }); + async function getTiffData() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat', + ); + const arrayBuffer = await response.arrayBuffer(); + return arrayBuffer; + } + scene.on('loaded', async () => { + const tiffdata = await getTiffData(); + const tiff = await GeoTIFF.fromArrayBuffer(tiffdata); + const image = await tiff.getImage(); + const width = image.getWidth(); + const height = image.getHeight(); + const values = await image.readRasters(); + console.log(values); + + const layer = new RasterLayer(); + layer + .source(values[0], { + parser: { + type: 'raster', + width, + height, + extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963], + }, + }) + .style({ + opacity: 1, + // clampLow: false, + // clampHigh: false, + domain: [0, 10000], + rampColors: { + type:'custom', + colors: ['#b2182b','#d6604d','#f4a582','#fddbc7','#f7f7f7','#d1e5f0','#92c5de','#4393c3','#2166ac'], + positions: [0, 50, 200, 500, 2000, 3000, 4000, 5000, 8000,10000], + }, + + }); + + scene.addLayer(layer); + scene.startAnimate() + }); + +} diff --git a/examples/demos/raster/index.ts b/examples/demos/raster/index.ts index 37b8e9bc65..86cd34c917 100644 --- a/examples/demos/raster/index.ts +++ b/examples/demos/raster/index.ts @@ -1,2 +1,3 @@ export { MapRender as image } from './image'; -export { MapRender as tiff } from './tiff'; \ No newline at end of file +export { MapRender as tiff } from './tiff'; +export { MapRender as dem } from './dem'; \ No newline at end of file diff --git a/examples/demos/raster/tiff.ts b/examples/demos/raster/tiff.ts index 84de027377..37169c7c7d 100644 --- a/examples/demos/raster/tiff.ts +++ b/examples/demos/raster/tiff.ts @@ -27,7 +27,7 @@ export function MapRender(option:{ }) { const scene = new Scene({ id: 'map', - renderer:option.renderer, + // renderer:option.renderer, map: new allMap[option.map || 'Map']({ center: [105, 37.5], zoom: 2.5, @@ -45,6 +45,7 @@ export function MapRender(option:{ zIndex: 2, visible: true, }); + console.log(tiffdata) layer .source(tiffdata.data, { parser: { diff --git a/examples/demos/webgpu/boids.ts b/examples/demos/webgpu/boids.ts new file mode 100644 index 0000000000..8598bf1837 --- /dev/null +++ b/examples/demos/webgpu/boids.ts @@ -0,0 +1,405 @@ +import { + Device, + Bindings, + BufferUsage, + Buffer, + BufferFrequencyHint, + VertexStepMode, + Format, + TextureDimension, + PrimitiveTopology, + TextureUsage, + TransparentWhite, + MipmapFilterMode, + AddressMode, + FilterMode, + ChannelWriteMask, + BlendMode, + BlendFactor, + CullMode, + WebGPUDeviceContribution, +} from '@antv/g-device-api'; +import { generateTexture } from './utils/common' +export async function MapRender(option: { + map: string + renderer: string +}) { + const dom = document.getElementById('map') as HTMLDivElement; + // 创建 canvas + const $canvas = document.createElement('canvas'); + dom.appendChild($canvas); + // 设置 canvas 大小 + $canvas.width = dom.clientWidth; + $canvas.height = dom.clientHeight; + const deviceContribution = new WebGPUDeviceContribution({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', + }); + const swapChain = await deviceContribution.createSwapChain($canvas); + swapChain.configureSwapChain($canvas.width, $canvas.height); + const device = swapChain.getDevice(); + + const renderProgram = device.createProgram({ + vertex: { + entryPoint: "vert_main", + wgsl: + /* wgsl */` + struct VertexOutput { + @builtin(position) position : vec4, + // @location(0) color : vec4, + @location(1) uv: vec2, + } + @group(1) @binding(0) var myTexture: texture_2d; + @group(1) @binding(1) var mySampler: sampler; + + @vertex + fn vert_main( + @location(0) a_particlePos : vec2, + @location(1) a_particleVel : vec2, + @location(2) a_pos : vec2, + ) -> VertexOutput { + let angle = -atan2(a_particleVel.x, a_particleVel.y); + + // var wind: vec2 = textureSample(myTexture, mySampler, a_particlePos).xy; + let pos = vec2( + (a_pos.x * cos(angle)) - (a_pos.y * sin(angle)), + (a_pos.x * sin(angle)) + (a_pos.y * cos(angle)) + ); + + var output : VertexOutput; + output.position = vec4(pos + a_particlePos, 0.0, 1.0); + // output.color = vec4( + // 1.0 - sin(angle + 1.0) - a_particleVel.y, + // pos.x * 100.0 - a_particleVel.y + 0.1, + // a_particleVel.x + cos(angle + 0.5), + // 1.0); + output.uv = a_particlePos; + return output; + } + ` + }, + fragment: { + entryPoint: "frag_main", + wgsl: + /* wgsl */` + @group(1) @binding(0) var myTexture: texture_2d; + @group(1) @binding(1) var mySampler: sampler; + @fragment + fn frag_main( + @location(1) uv : vec2 + ) -> @location(0) vec4 { + // return color; + return textureSample(myTexture, mySampler, uv); + } + ` + } + }); + + const computeProgram = device.createProgram({ + compute: { + wgsl: + /* wgsl */ ` + struct Particle { + pos : vec2, + vel : vec2, + } + struct SimParams { + deltaT : f32, + rule1Distance : f32, + rule2Distance : f32, + rule3Distance : f32, + rule1Scale : f32, + rule2Scale : f32, + rule3Scale : f32, + + } + struct Particles { + particles : array, + } + @binding(0) @group(0) var params : SimParams; + + @binding(0) @group(1) var myTexture: texture_2d; + + @binding(0) @group(2) var particlesA : Particles; + @binding(1) @group(2) var particlesB : Particles; + + // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp + @compute @workgroup_size(64) + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3) { + var index = GlobalInvocationID.x; + + var vPos = particlesA.particles[index].pos; // 速度来自纹理 + + // var vVel = particlesA.particles[index].vel; + var vVel = textureLoad(myTexture, vec2(vPos), 0).rg; + var cMass = vec2(0.0); + var cVel = vec2(0.0); + var colVel = vec2(0.0); + var cMassCount = 0u; + var cVelCount = 0u; + var pos : vec2; + var vel : vec2; + var u_wind_min= vec2(-20.26,23.24); + var v_wind_max= vec2(-20.41,19.66); + + for (var i = 0u; i < arrayLength(&particlesA.particles); i++) { + if (i == index) { + continue; + } + + pos = particlesA.particles[i].pos.xy; + // vel = particlesA.particles[i].vel.xy; + vVel = textureLoad(myTexture, vec2(pos), 0).rg; + if (distance(pos, vPos) < params.rule1Distance) { + cMass += pos; + cMassCount++; + } + if (distance(pos, vPos) < params.rule2Distance) { + colVel -= pos - vPos; + } + if (distance(pos, vPos) < params.rule3Distance) { + cVel += vel; + cVelCount++; + } + } + if (cMassCount > 0) { + cMass = (cMass / vec2(f32(cMassCount))) - vPos; + } + if (cVelCount > 0) { + cVel /= f32(cVelCount); + } + vVel += (cMass * params.rule1Scale) + (colVel * params.rule2Scale) + (cVel * params.rule3Scale); + + // clamp velocity for a more pleasing simulation + vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1); + // kinematic update + vPos = vPos + (vVel * params.deltaT); + // Wrap around boundary + if (vPos.x < -1.0) { + vPos.x = 1.0; + } + if (vPos.x > 1.0) { + vPos.x = -1.0; + } + if (vPos.y < -1.0) { + vPos.y = 1.0; + } + if (vPos.y > 1.0) { + vPos.y = -1.0; + } + // Write back + particlesB.particles[index].pos = vPos; + particlesB.particles[index].vel = vVel; + } + ` + } + }); + const imageUrl ='https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*p4PURaZpM-cAAAAAAAAAAAAADmJ7AQ/original'; + const windTexture = await generateTexture(device, imageUrl) + + const numParticles = 1500; + const initialParticleData = new Float32Array(numParticles * 4); + for (let i = 0; i < numParticles; ++i) { + initialParticleData[4 * i + 0] = 2 * (Math.random() - 0.5); + initialParticleData[4 * i + 1] = 2 * (Math.random() - 0.5); + initialParticleData[4 * i + 2] = 2 * (Math.random() - 0.5) * 0.1; + initialParticleData[4 * i + 3] = 2 * (Math.random() - 0.5) * 0.1; + } + + const particleBuffers: Buffer[] = []; + for (let i = 0; i < 2; ++i) { + particleBuffers[i] = device.createBuffer({ + viewOrSize: initialParticleData, + usage: BufferUsage.VERTEX | BufferUsage.STORAGE + }); + } + + const vertexBufferData = new Float32Array([ + -0.01, -0.02, 0.01, -0.02, 0.0, 0.02 + ]); + const spriteVertexBuffer = device.createBuffer({ + viewOrSize: vertexBufferData, + usage: BufferUsage.VERTEX + }); + + const uniformBuffer = device.createBuffer({ + viewOrSize: 7 * Float32Array.BYTES_PER_ELEMENT, + usage: BufferUsage.UNIFORM + }); + + const inputLayout = device.createInputLayout({ + vertexBufferDescriptors: [ + { + arrayStride: 4 * 4, + stepMode: VertexStepMode.INSTANCE, + attributes: [ + { + // instance position + shaderLocation: 0, + offset: 0, + format: Format.F32_RG + }, + { + // instance velocity + shaderLocation: 1, + offset: 4 * 2, + format: Format.F32_RG + } + ] + }, + { + arrayStride: 4 * 2, + stepMode: VertexStepMode.VERTEX, + attributes: [ + { + // vertex positions + shaderLocation: 2, + offset: 0, + format: Format.F32_RG + } + ] + } + ], + + indexBufferFormat: null, + program: renderProgram + }); + + const renderPipeline = device.createRenderPipeline({ + inputLayout, + program: renderProgram, + colorAttachmentFormats: [Format.U8_RGBA_RT] + }); + const computePipeline = device.createComputePipeline({ + inputLayout: null, + program: computeProgram + }); + + const simParams = { + deltaT: 0.04, + rule1Distance: 0.1, + rule2Distance: 0.025, + rule3Distance: 0.025, + rule1Scale: 0.02, + rule2Scale: 0.05, + rule3Scale: 0.005 + }; + + const bindings: Bindings[] = []; + for (let i = 0; i < 2; ++i) { + bindings[i] = device.createBindings({ + pipeline: computePipeline, + uniformBufferBindings: [ + { + binding: 0, + buffer: uniformBuffer, + size: 7 * Float32Array.BYTES_PER_ELEMENT + } + ], + samplerBindings:[{ + texture:windTexture, + sampler:null, + samplerBinding: -1 + }], + storageBufferBindings: [ + { + binding: 0, + buffer: particleBuffers[i], + size: initialParticleData.byteLength + }, + { + binding: 1, + buffer: particleBuffers[(i + 1) % 2], + size: initialParticleData.byteLength + } + ] + }); + } + const sampler = device.createSampler({ + addressModeU: AddressMode.CLAMP_TO_EDGE, + addressModeV: AddressMode.CLAMP_TO_EDGE, + minFilter: FilterMode.POINT, + magFilter: FilterMode.BILINEAR, + mipmapFilter: MipmapFilterMode.LINEAR, + lodMinClamp: 0, + lodMaxClamp: 0 + }); + + const renderbindings = device.createBindings({ + pipeline:renderPipeline, + samplerBindings: [ + { + texture: windTexture, + sampler + } + ] + }); + + const renderTarget = device.createRenderTarget({ + format: Format.U8_RGBA_RT, + width: $canvas.width, + height: $canvas.height + }); + device.setResourceName(renderTarget, "Main Render Target"); + + uniformBuffer.setSubData( + 0, + new Uint8Array( + new Float32Array([ + simParams.deltaT, + simParams.rule1Distance, + simParams.rule2Distance, + simParams.rule3Distance, + simParams.rule1Scale, + simParams.rule2Scale, + simParams.rule3Scale + ]).buffer + ) + ); + + let id; + let t = 0; + const frame = () => { + const computePass = device.createComputePass(); + computePass.setPipeline(computePipeline); + computePass.setBindings(bindings[t % 2]); + computePass.dispatchWorkgroups(Math.ceil(numParticles / 64)); + device.submitPass(computePass); + + /** + * An application should call getCurrentTexture() in the same task that renders to the canvas texture. + * Otherwise, the texture could get destroyed by these steps before the application is finished rendering to it. + */ + const onscreenTexture = swapChain.getOnscreenTexture(); + const renderPass = device.createRenderPass({ + colorAttachment: [renderTarget], + colorResolveTo: [onscreenTexture], + colorClearColor: [TransparentWhite] + }); + renderPass.setPipeline(renderPipeline); + renderPass.setVertexInput( + inputLayout, + [ + { + buffer: particleBuffers[(t + 1) % 2] + }, + { + buffer: spriteVertexBuffer + } + ], + null + ); + renderPass.setViewport(0, 0, $canvas.width, $canvas.height); + renderPass.setBindings(renderbindings); + renderPass.draw(3, numParticles); + + device.submitPass(renderPass); + ++t; + id = requestAnimationFrame(frame); + }; + + frame(); + + +} + + diff --git a/examples/demos/webgpu/compute_texture.ts b/examples/demos/webgpu/compute_texture.ts new file mode 100644 index 0000000000..eb320abeaa --- /dev/null +++ b/examples/demos/webgpu/compute_texture.ts @@ -0,0 +1,261 @@ +import { + Device, + Bindings, + BufferUsage, + Buffer, + BufferFrequencyHint, + VertexStepMode, + Format, + TextureDimension, + PrimitiveTopology, + TextureUsage, + TransparentWhite, + MipmapFilterMode, + AddressMode, + FilterMode, + ChannelWriteMask, + BlendMode, + BlendFactor, + CullMode, + WebGPUDeviceContribution, +} from '@antv/g-device-api'; + +import { createTexture, generateTexture } from './utils/common'; +export async function MapRender(option: { + map: string + renderer: string +}) { + const dom = document.getElementById('map') as HTMLDivElement; + // 创建 canvas + const $canvas = document.createElement('canvas'); + dom.appendChild($canvas); + // 设置 canvas 大小 + $canvas.width = dom.clientWidth; + $canvas.height = dom.clientHeight; + const deviceContribution = new WebGPUDeviceContribution({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', + }); + const swapChain = await deviceContribution.createSwapChain($canvas); + swapChain.configureSwapChain($canvas.width, $canvas.height); + + const device = swapChain.getDevice(); + + + + // 计算 + const computeProgram = device.createProgram({ + compute: { + wgsl: + /* wgsl */` + struct windParams { + u_wind_min : vec2, + u_wind_max : vec2, + u_rand_seed : f32, + u_speed_factor : f32, + u_drop_rate : f32, + u_drop_rate_bump : f32, + u_particles_res: f32, + u_wind_res : vec2, + + } + + @binding(0) @group(0) var params : windParams; + @group(1) @binding(2) var u_particles : texture_2d;; + @binding(2) @group(1) var u_wind: texture_2d; + @binding(3) @group(1) var outputTex : texture_storage_2d; + @binding(2) @group(1) var u_sampler: sampler; + @binding(0) @group(2) var particlesA:array; + + const rand_constants: vec3 = vec3(12.9898, 78.233, 4375.85453); + + // WGSL 不使用 "fract",使用 "%" 替代 "fract" + fn rand(co: vec2) -> f32 { + let t: f32 = dot(rand_constants.xy, co); + return t - t; + } + + // WGSL 不使用 "texture2D",使用 "textureSample" 替代 + fn lookup_wind(uv: vec2) -> vec2 { + let px: vec2 = 1.0 / params.u_wind_res; + let vc: vec2 = floor(uv * params.u_wind_res) * px; + let f: vec2 = fract(uv * params.u_wind_res); + let tl: vec2 = textureSample(u_wind, u_sampler, vc).rg; + let tr: vec2 = textureSample(u_wind, u_sampler, vc + vec2(px.x, 0.0)).rg; + let bl: vec2 = textureSample(u_wind, u_sampler, vc + vec2(0.0, px.y)).rg; + let br: vec2 = textureSample(u_wind, u_sampler, vc + px).rg; + return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y); + } + + // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp + @compute @workgroup_size(64) + fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3) { + let a_index = GlobalInvocationID.x; + let v_tex_pos:vec2 = vec2( + fract(f32(a_index)/ params.u_particles_res), + floor(f32(a_index) / params.u_particles_res) / params.u_particles_res) * params.u_wind_res; + + let color = textureLoad(u_particles,v_tex_pos,0); + + + + // 解码粒子位置 + var pos: vec2 = vec2( + color.r / 255.0 + color.b, + color.g / 255.0 + color.a); + + // 获取风速,并根据最小值和最大值进行插值 + var velocity: vec2 = mix(params.u_wind_min, params.u_wind_max, lookup_wind(pos)); + let speed_t: f32 = length(velocity) / length(params.u_wind_max); + + // 考虑EPSG:4236扭曲,计算粒子移动的偏移 + let distortion: f32 = cos(radians(pos.y * 180.0 - 90.0)); + let offset: vec2 = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * params.u_speed_factor; + + // 更新粒子位置,绕过日期线 + pos = pos + offset; + pos = pos - floor(pos); + + // 用于粒子重新开始的随机种子 + let seed: vec2 = (pos + v_tex_pos) * params.u_rand_seed; + + // 粒子掉落速率,防止粒子过度集中 + let drop_rate: f32 = params.u_drop_rate + speed_t * params.u_drop_rate_bump; + let drop: f32 = step(1.0 - drop_rate, rand(seed)); + + // 生成随机位置 + let random_pos: vec2 = vec2( + rand(seed + vec2(1.3, 2.1)), + rand(seed + vec2(2.1, 1.3))); + pos = mix(pos, random_pos, drop); + + // 将新的粒子位置编码回RGBA + textureStore(outputTex, v_tex_pos, vec4(fract(pos * 255.0), floor(pos * 255.0) / 255.0)); + } + + + ` + } + }); + + + const numParticles = 65536; + const particleRes = Math.ceil(Math.sqrt(numParticles)); + const numParticles2 = particleRes * particleRes; + const particleState = new Uint8Array(numParticles2 * 4); + for (let i = 0; i < particleState.length; i++) { + particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions + } + + const particleStateTexture0 = createTexture(device, particleRes, particleRes, particleState); + const particleStateTexture1 = createTexture(device, particleRes, particleRes, particleState); + const particleIndices = new Float32Array(this._numParticles); + for (let i = 0; i < this._numParticles; i++) particleIndices[i] = i; + + const particleBuffer = device.createBuffer({ + viewOrSize: particleIndices, + usage: BufferUsage.VERTEX | BufferUsage.STORAGE + }); + + + const imageUrl = 'https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*p4PURaZpM-cAAAAAAAAAAAAADmJ7AQ/original'; + const windTexture = await generateTexture(device, imageUrl) + + + // const sampler = device.createSampler({ + // addressModeU: AddressMode.CLAMP_TO_EDGE, + // addressModeV: AddressMode.CLAMP_TO_EDGE, + // minFilter: FilterMode.POINT, + // magFilter: FilterMode.BILINEAR, + // mipmapFilter: MipmapFilterMode.LINEAR, + // lodMinClamp: 0, + // lodMaxClamp: 0 + // }); + + + // const computePipeline = device.createComputePipeline({ + // inputLayout: null, + // program: computeProgram + // }); + + // const simParams = { + // deltaT: 0.04, + // rule1Distance: 0.1, + // rule2Distance: 0.025, + // rule3Distance: 0.025, + // rule1Scale: 0.02, + // rule2Scale: 0.05, + // rule3Scale: 0.005 + // }; + + // const uniformBuffer = device.createBuffer({ + // viewOrSize: 7 * Float32Array.BYTES_PER_ELEMENT, + // usage: BufferUsage.UNIFORM + // }); + + // const bindings: Bindings[] = []; + // for (let i = 0; i < 2; ++i) { + // bindings[i] = device.createBindings({ + // pipeline: computePipeline, + // uniformBufferBindings: [ + // { + // binding: 0, + // buffer: uniformBuffer, + // size: 7 * Float32Array.BYTES_PER_ELEMENT + // } + // ], + // samplerBindings: [{ + // texture, + // sampler, + // samplerBinding: -1 + // }], + // storageBufferBindings: [ + // { + // binding: 0, + // buffer: particleBuffers[i], + // size: initialParticleData.byteLength + // }, + // { + // binding: 1, + // buffer: particleBuffers[(i + 1) % 2], + // size: initialParticleData.byteLength + // } + // ] + // }); + // } + + + + // uniformBuffer.setSubData( + // 0, + // new Uint8Array( + // new Float32Array([ + // simParams.deltaT, + // simParams.rule1Distance, + // simParams.rule2Distance, + // simParams.rule3Distance, + // simParams.rule1Scale, + // simParams.rule2Scale, + // simParams.rule3Scale + // ]).buffer + // ) + // ); + + // let id; + // let t = 0; + // const frame = () => { + // const computePass = device.createComputePass(); + // computePass.setPipeline(computePipeline); + // computePass.setBindings(bindings[t % 2]); + // computePass.dispatchWorkgroups(Math.ceil(numParticles / 64)); + // device.submitPass(computePass); + // ++t; + // id = requestAnimationFrame(frame); + // }; + // const readback = device.createReadback(); + // const data = await readback.readBuffer(particleBuffers[1], 0, new Float32Array(numParticles*4)); + // console.log(data); + // frame(); + +} + + diff --git a/examples/demos/webgpu/index.ts b/examples/demos/webgpu/index.ts index caca3e3f9c..8630992c47 100644 --- a/examples/demos/webgpu/index.ts +++ b/examples/demos/webgpu/index.ts @@ -1 +1,4 @@ export { MapRender as WebGL_IDW } from './idw'; +export { MapRender as compute_texture } from './compute_texture'; +export { MapRender as boids } from './boids'; +export { MapRender as texture } from './texture'; diff --git a/examples/demos/webgpu/template.ts b/examples/demos/webgpu/template.ts new file mode 100644 index 0000000000..753c8dc8d4 --- /dev/null +++ b/examples/demos/webgpu/template.ts @@ -0,0 +1,44 @@ +import { + Device, + Bindings, + BufferUsage, + Buffer, + BufferFrequencyHint, + VertexStepMode, + Format, + TextureDimension, + PrimitiveTopology, + TextureUsage, + TransparentWhite, + MipmapFilterMode, + AddressMode, + FilterMode, + ChannelWriteMask, + BlendMode, + BlendFactor, + CullMode, + WebGPUDeviceContribution, + } from '@antv/g-device-api'; +export async function MapRender(option: { + map: string + renderer: string +}) { + const dom = document.getElementById('map') as HTMLDivElement; + // 创建 canvas + const $canvas = document.createElement('canvas'); + dom.appendChild($canvas); + // 设置 canvas 大小 + $canvas.width = dom.clientWidth; + $canvas.height = dom.clientHeight; + const deviceContribution = new WebGPUDeviceContribution({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', + }); + const swapChain = await deviceContribution.createSwapChain($canvas); + swapChain.configureSwapChain($canvas.width, $canvas.height); + const device = swapChain.getDevice(); + + + +} + + diff --git a/examples/demos/webgpu/texture.ts b/examples/demos/webgpu/texture.ts new file mode 100644 index 0000000000..0d77185a05 --- /dev/null +++ b/examples/demos/webgpu/texture.ts @@ -0,0 +1,211 @@ +import { + Device, + Bindings, + BufferUsage, + Buffer, + BufferFrequencyHint, + VertexStepMode, + Format, + TextureDimension, + PrimitiveTopology, + TextureUsage, + TransparentWhite, + MipmapFilterMode, + AddressMode, + FilterMode, + ChannelWriteMask, + BlendMode, + BlendFactor, + CullMode, + WebGPUDeviceContribution, +} from '@antv/g-device-api'; +import { createTexture, generateTexture } from './utils/common'; +export async function MapRender(option: { + map: string + renderer: string +}) { + const dom = document.getElementById('map') as HTMLDivElement; + // 创建 canvas + const $canvas = document.createElement('canvas'); + dom.appendChild($canvas); + // 设置 canvas 大小 + $canvas.width = dom.clientWidth; + $canvas.height = dom.clientHeight; + const deviceContribution = new WebGPUDeviceContribution({ + shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', + }); + const swapChain = await deviceContribution.createSwapChain($canvas); + swapChain.configureSwapChain($canvas.width, $canvas.height); + const device = swapChain.getDevice(); + const program = device.createProgram({ + vertex: { + entryPoint: 'main', + wgsl: ` + struct VertexOutput { + @location(1) uv: vec2, + @builtin(position) position : vec4, + + } + + + @vertex + fn main( + @location(0) pos: vec2f, + @location(1) uv: vec2 + ) -> VertexOutput { + var output : VertexOutput; + output.position = vec4(pos.xy, 0.0, 1.0); + output.uv = uv; + return output; + } + ` + }, + fragment: { + entryPoint: 'main', + wgsl: ` + @group(1) @binding(1) var mySampler: sampler; + @group(1) @binding(0) var myTexture: texture_2d;; + + @fragment + fn main( + @location(1) uv : vec2 + ) -> @location(0) vec4 { + return textureSample(myTexture, mySampler, uv); + } + ` + } + }); + + async function loadImage(url) { + const img = new Image(); + const imgBitmap = await createImageBitmap(await fetch(url).then(response => response.blob())); + return imgBitmap; + } + + // 创建纹理和纹理视图 + const url ='https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*p4PURaZpM-cAAAAAAAAAAAAADmJ7AQ/original' + const texture = await generateTexture(device,url); + const numParticles = 6553600; + const particleRes = Math.ceil(Math.sqrt(numParticles)); + const numParticles2 = particleRes * particleRes; + const particleState = new Uint8ClampedArray(numParticles2 * 4); + for (let i = 0; i < particleState.length; i++) { + particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions + } + const particleStateTexture0 = await createTexture(device, particleRes, particleRes, particleState); + + + const sampler = device.createSampler({ + addressModeU: AddressMode.CLAMP_TO_EDGE, + addressModeV: AddressMode.CLAMP_TO_EDGE, + minFilter: FilterMode.POINT, + magFilter: FilterMode.BILINEAR, + mipmapFilter: MipmapFilterMode.LINEAR, + lodMinClamp: 0, + lodMaxClamp: 0 + }); + + + const vertexBuffer = device.createBuffer({ + viewOrSize: new Float32Array([ + -1, 1, 0.0, 1.0, // 左上角 + -1, -1, 0.0, 0.0, // 左下角 + 1, -1, 1.0, 0.0, // 右下角 + + // 第二个三角形 + 1, 1, 1.0, 1.0,// 右上角 + + ]), + usage: BufferUsage.VERTEX, + hint: BufferFrequencyHint.DYNAMIC + + }); + device.setResourceName(vertexBuffer, "a_Position"); + const indexBuffer = device.createBuffer({ + viewOrSize: new Uint32Array([0, 1, 2, 0, 2, 3]), + usage: BufferUsage.INDEX, + hint: BufferFrequencyHint.STATIC + }); + const inputLayout = device.createInputLayout({ + vertexBufferDescriptors: [{ + arrayStride: 4 * 4, + stepMode: VertexStepMode.VERTEX, + attributes: [ + { + shaderLocation: 0, + offset: 0, + format: Format.F32_RG + }, { + shaderLocation: 1, + offset: 4 * 2, + format: Format.F32_RG + } + ] + }, + ], + indexBufferFormat: Format.U32_R, + program + }); + + + + const pipeline = device.createRenderPipeline({ + inputLayout, + program, + colorAttachmentFormats: [Format.U8_RGBA_RT] + }); + + const bindings = device.createBindings({ + pipeline, + samplerBindings: [ + { + texture:particleStateTexture0, + sampler + } + ] + }); + + const renderTarget = device.createRenderTargetFromTexture( + device.createTexture({ + format: Format.U8_RGBA_RT, + width: $canvas.width, + height: $canvas.height, + usage: TextureUsage.RENDER_TARGET + }) + ); + device.setResourceName(renderTarget, "Main Render Target"); + + const onscreenTexture = swapChain.getOnscreenTexture(); + + const renderPass = device.createRenderPass({ + colorAttachment: [renderTarget], + colorResolveTo: [onscreenTexture], + colorClearColor: [TransparentWhite] + }); + + renderPass.setPipeline(pipeline); + renderPass.setVertexInput( + inputLayout, + [ + { + buffer: vertexBuffer + } + ], + { + buffer: indexBuffer, + offset: 0 + } + ); + renderPass.setViewport(0, 0, $canvas.width, $canvas.height); + renderPass.setBindings(bindings); + renderPass.drawIndexed(6); + + device.submitPass(renderPass); + + + + + +} + + diff --git a/examples/demos/webgpu/utils/common.ts b/examples/demos/webgpu/utils/common.ts new file mode 100644 index 0000000000..3861b7e7a8 --- /dev/null +++ b/examples/demos/webgpu/utils/common.ts @@ -0,0 +1,45 @@ +import { Device, Format, TextureUsage, TextureDimension } from "@antv/g-device-api"; + +async function loadImage(url) { + const imgBitmap = await createImageBitmap(await fetch(url).then(response => response.blob())); + return imgBitmap; +} +export async function generateTexture(device: Device, url: string) { + // 创建纹理和纹理视图 + const image = await loadImage('https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*p4PURaZpM-cAAAAAAAAAAAAADmJ7AQ/original') + + const texture = device.createTexture({ + format: Format.F32_RGBA, + width: image.width, + height: image.height, + usage: TextureUsage.SAMPLED, + dimension: TextureDimension.TEXTURE_2D, + mipLevelCount: 1, + }); + texture.setImageData([image]); + return texture; +} + +export async function createTexture(device: Device, width: number, height: number, data: Uint8Array) { + // 创建纹理和纹理视图 + const texture = device.createTexture({ + format: Format.U8_RGBA_NORM, + width, + height, + usage: TextureUsage.SAMPLED, + dimension: TextureDimension.TEXTURE_2D, + mipLevelCount: 1, + }); + // @ts-ignore + const rawDevice = device.device; + rawDevice.queue.writeTexture( + // @ts-ignore + { texture:texture.gpuTexture }, + data, + { bytesPerRow: width * 4 }, + { width: width, height: height }, + ); + + // texture.setImageData([img]); + return texture; +} \ No newline at end of file diff --git a/package.json b/package.json index 423c836650..c709735014 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "dev-demos/*" ], "scripts": { - "vite-dev": "VITE_RENDERER='device' vite dev", - "vite-build": "vite build", - "dev": "export NODE_OPTIONS=--openssl-legacy-provider && dumi dev", + "dev": "vite dev", + "dev_build": "vite build", + "dev-dumo": "export NODE_OPTIONS=--openssl-legacy-provider && dumi dev", "dev-device": "export NODE_OPTIONS=--openssl-legacy-provider renderer=device && dumi dev renderer=device", "dev_windows": "dumi dev", - "dev-build": "export NODE_OPTIONS=--openssl-legacy-provider && dumi build", + "dumi-build": "export NODE_OPTIONS=--openssl-legacy-provider && dumi build", "dev:ci": "vite dev", "start": "lerna --scope @antv/l7-site exec yarn run site:develop", "site:build": "lerna --scope @antv/l7-site exec yarn run site:build", diff --git a/packages/layers/src/raster/models/raster.ts b/packages/layers/src/raster/models/raster.ts index 8189a540ab..e19a157f3c 100644 --- a/packages/layers/src/raster/models/raster.ts +++ b/packages/layers/src/raster/models/raster.ts @@ -90,11 +90,11 @@ export default class RasterModel extends BaseModel { const { data, width, height } = await this.getRasterData(parserDataItem); this.texture = createTexture2D({ - data: new Uint8Array(data), + data, width, height, format: gl.LUMINANCE, - type: gl.UNSIGNED_BYTE, + type: gl.FLOAT, alignment: 1, // aniso: 4, }); diff --git a/packages/layers/src/raster/shaders/raster/raster_2d_frag.glsl b/packages/layers/src/raster/shaders/raster/raster_2d_frag.glsl index dd498d797e..59d123ad73 100644 --- a/packages/layers/src/raster/shaders/raster/raster_2d_frag.glsl +++ b/packages/layers/src/raster/shaders/raster/raster_2d_frag.glsl @@ -17,7 +17,7 @@ out vec4 outputColor; void main() { // Can use any component here since u_rasterTexture is under luminance format. - float value = texture(SAMPLER_2D(u_rasterTexture), vec2(v_texCoord.x, v_texCoord.y)).r * 255.0; + float value = texture(SAMPLER_2D(u_rasterTexture), vec2(v_texCoord.x, v_texCoord.y)).r; if (value == u_noDataValue || isnan_emu(value)) { discard; } else if ((!u_clampLow && value < u_domain[0]) || (!u_clampHigh && value > u_domain[1])) { diff --git a/packages/renderer/package.json b/packages/renderer/package.json index 9329b42476..e6a526db44 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -24,7 +24,7 @@ "tsc": "tsc --project tsconfig.build.json" }, "dependencies": { - "@antv/g-device-api": "^1.4.5", + "@antv/g-device-api": "^1.4.10", "@antv/l7-core": "2.20.9", "@antv/l7-utils": "2.20.9", "@babel/runtime": "^7.7.7",