diff options
| author | Tengu712 <genreihoutengu@gmail.com> | 2026-09-26 21:36:43 +0900 |
|---|---|---|
| committer | Tengu712 <genreihoutengu@gmail.com> | 2026-09-26 21:36:43 +0900 |
| commit | f6d058be6639c8e052c5a38dea4f33d6b26ae20c (patch) | |
| tree | f101cc3ff0a9c620274360672973943d710e9a9d | |
| parent | b27234e27bbaf4d7f7f3467de64bd95ff2145c0e (diff) | |
add texture
| -rw-r--r-- | src/graphics.rs | 68 | ||||
| -rw-r--r-- | src/graphics/texture.rs | 108 | ||||
| -rw-r--r-- | src/shader.wgsl | 21 |
3 files changed, 191 insertions, 6 deletions
diff --git a/src/graphics.rs b/src/graphics.rs index 6fa13b3..bbe07df 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -2,6 +2,10 @@ use std::{borrow::Cow, sync::Arc}; use wgpu::*; use winit::{dpi::PhysicalSize, event_loop::OwnedDisplayHandle, window::Window}; +mod texture; + +use texture::TextureWrapper; + pub enum AcquireSurfaceTextureResult { Ok(SurfaceTexture), ShouldSkip, @@ -16,6 +20,9 @@ pub struct Renderer { view_format: TextureFormat, present_mode: PresentMode, pipeline: RenderPipeline, + + font_tex: TextureWrapper, + fixed_bind_group: BindGroup, } impl Renderer { @@ -26,7 +33,6 @@ impl Renderer { let size = window.inner_size(); let instance = Instance::new(InstanceDescriptor::new_with_display_handle(display)); - let surface = instance.create_surface(window).map_err(|e| e.to_string())?; let adapter = instance @@ -67,9 +73,30 @@ impl Renderer { source: ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); + let fixed_bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: None, + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + ], + }); let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { label: None, - bind_group_layouts: &[], + bind_group_layouts: &[Some(&fixed_bind_group_layout)], immediate_size: 0, }); let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { @@ -98,6 +125,40 @@ impl Renderer { cache: None, }); + // NOTE: 本ゲームではlinearなサンプラしか用意しないので、ここで1個だけ作成すれば良い。 + let sampler = device.create_sampler(&SamplerDescriptor { + label: None, + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + address_mode_w: AddressMode::ClampToEdge, + mag_filter: FilterMode::Linear, + min_filter: FilterMode::Linear, + mipmap_filter: MipmapFilterMode::Linear, + ..Default::default() + }); + // NOTE: 文字テクスチャアトラスは使い回すので、ここで1個だけ作成すれば良い。 + let font_tex = TextureWrapper::new( + &device, + 128, + 128, + TextureFormat::R8Unorm, + TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + ); + let fixed_bind_group = device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &fixed_bind_group_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::Sampler(&sampler), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(&font_tex.view), + }, + ], + }); + let v = Self { device, queue, @@ -106,6 +167,8 @@ impl Renderer { view_format, present_mode, pipeline, + font_tex, + fixed_bind_group, }; v.configure_surface(size); @@ -161,6 +224,7 @@ impl Renderer { }); render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.fixed_bind_group, &[]); // TODO: 描画 diff --git a/src/graphics/texture.rs b/src/graphics/texture.rs new file mode 100644 index 0000000..6273b6e --- /dev/null +++ b/src/graphics/texture.rs @@ -0,0 +1,108 @@ +use wgpu::*; + +pub struct TextureWrapper { + pub width: u32, + pub height: u32, + pub block_size: u32, + pub texture: Texture, + pub view: TextureView, +} + +impl TextureWrapper { + pub fn new( + device: &Device, + width: u32, + height: u32, + format: TextureFormat, + usage: TextureUsages, + ) -> Self { + let texture = device.create_texture(&TextureDescriptor { + label: None, + size: Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format, + usage, + view_formats: &[], + }); + let view = texture.create_view(&TextureViewDescriptor::default()); + + // NOTE: Depth, Stencil, YUVでない限りはNoneにはならないが、念の為`1`にunwrapする。 + let block_size = format.block_copy_size(None).unwrap_or(1); + + Self { + width, + height, + block_size, + texture, + view, + } + } + + /// テクスチャ全体を更新するメソッド + /// + /// * queue - queue + /// * data - 更新データ + /// + /// `data`がテクスチャサイズと一致しない場合は、クラッシュしないように不足分が白色で埋められる。 + pub fn write(&self, queue: &Queue, data: &[u8]) { + self.write_partial(queue, data, Origin3d::ZERO, self.width, self.height); + } + + /// テクスチャを部分的に更新するメソッド + /// + /// * queue - queue + /// * data - 更新データ + /// * origin - 更新領域の開始座標 + /// * width - 更新領域の幅 + /// * height - 更新領域の高さ + /// + /// `data`が更新領域サイズと一致しない場合は、クラッシュしないように不足分が白色で埋められる。 + pub fn write_partial( + &self, + queue: &Queue, + data: &[u8], + origin: Origin3d, + width: u32, + height: u32, + ) { + let expected_len = (width * height * self.block_size) as usize; + + // `data`が想定サイズより短い場合はエラーになるので不足分を白色で埋める。 + let buffer; + let data = if data.len() >= expected_len { + data + } else { + let mut v = Vec::with_capacity(expected_len); + v.extend_from_slice(data); + v.resize(expected_len, 0xff); + buffer = v; + buffer.as_slice() + }; + + queue.write_texture( + TexelCopyTextureInfo { + texture: &self.texture, + mip_level: 0, + origin, + aspect: TextureAspect::All, + }, + data, + TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(width * self.block_size), + rows_per_image: Some(height), + }, + Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + ); + } +} diff --git a/src/shader.wgsl b/src/shader.wgsl index 9f158f1..c154cc4 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,10 +1,23 @@ +@group(0) @binding(0) var tex_sampler: sampler; +@group(0) @binding(1) var font_tex: texture_2d<f32>; + +struct VertexOutput { + @builtin(position) position: vec4<f32>, + @location(0) uv: vec2<f32>, +} + @vertex -fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> { +fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput { let uv = vec2<f32>(f32((in_vertex_index << 1) & 2), f32(in_vertex_index & 2)) / 2.0; - return vec4<f32>(uv.x - 0.5, uv.y - 0.5, 0.0, 1.0); + + var out: VertexOutput; + out.position = vec4<f32>(uv.x - 0.5, uv.y - 0.5, 0.0, 1.0); + out.uv = uv; + + return out; } @fragment -fn fs_main() -> @location(0) vec4<f32> { - return vec4<f32>(1.0, 1.0, 1.0, 1.0); +fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 1.0, 1.0, textureSample(font_tex, tex_sampler, in.uv).r); } |
