rustraight 0.5.2

A simple 2D game library for Rust, inspired by DXLib
// pipelines.rs — init() と build_overlay() の間で重複していたパイプライン構築・
// レンダーターゲット生成ロジックを共通化する。ブレンド定数・頂点属性オフセットは
// 元のコードから値を変更せず転記している。

// ── 頂点レイアウト ────────────────────────────────────────────────────────────

const IMAGE_VERTEX_ATTRS: [wgpu::VertexAttribute; 9] = [
    wgpu::VertexAttribute { shader_location: 0, offset:  0, format: wgpu::VertexFormat::Float32x2 },
    wgpu::VertexAttribute { shader_location: 1, offset:  8, format: wgpu::VertexFormat::Float32x2 },
    wgpu::VertexAttribute { shader_location: 2, offset: 16, format: wgpu::VertexFormat::Float32x2 },
    wgpu::VertexAttribute { shader_location: 3, offset: 24, format: wgpu::VertexFormat::Float32   },
    wgpu::VertexAttribute { shader_location: 4, offset: 28, format: wgpu::VertexFormat::Float32   },
    wgpu::VertexAttribute { shader_location: 5, offset: 32, format: wgpu::VertexFormat::Float32   },
    wgpu::VertexAttribute { shader_location: 6, offset: 36, format: wgpu::VertexFormat::Float32   },
    wgpu::VertexAttribute { shader_location: 7, offset: 40, format: wgpu::VertexFormat::Float32   },
    wgpu::VertexAttribute { shader_location: 8, offset: 44, format: wgpu::VertexFormat::Float32   },
];

const COLOR_VERTEX_ATTRS: [wgpu::VertexAttribute; 2] = [
    wgpu::VertexAttribute { shader_location: 0, offset: 0, format: wgpu::VertexFormat::Float32x2 },
    wgpu::VertexAttribute { shader_location: 1, offset: 8, format: wgpu::VertexFormat::Float32x4 },
];

/// 画像頂点(ImageVertex, stride 48 bytes)のレイアウト。
pub(super) fn image_vertex_layout() -> wgpu::VertexBufferLayout<'static> {
    wgpu::VertexBufferLayout {
        array_stride: 48,
        step_mode:    wgpu::VertexStepMode::Vertex,
        attributes:   &IMAGE_VERTEX_ATTRS,
    }
}

/// 単色頂点(ColorVert, stride 24 bytes)のレイアウト。
pub(super) fn color_vertex_layout() -> wgpu::VertexBufferLayout<'static> {
    wgpu::VertexBufferLayout {
        array_stride: 24,
        step_mode:    wgpu::VertexStepMode::Vertex,
        attributes:   &COLOR_VERTEX_ATTRS,
    }
}

// ── ブレンド定数 ──────────────────────────────────────────────────────────────

/// 通常合成(straight alpha)。
pub(super) fn blend_normal() -> wgpu::BlendState { wgpu::BlendState::ALPHA_BLENDING }

/// 加算合成(straight alpha)。
pub(super) fn blend_add() -> wgpu::BlendState {
    wgpu::BlendState {
        color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add },
        alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One,      dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add },
    }
}

/// 乗算合成。premultiplied / straight のどちらでも同じ式になる。
pub(super) fn blend_mul() -> wgpu::BlendState {
    wgpu::BlendState {
        color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::Dst, dst_factor: wgpu::BlendFactor::Zero, operation: wgpu::BlendOperation::Add },
        alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::Zero, operation: wgpu::BlendOperation::Add },
    }
}

/// 通常合成(premultiplied alpha、gpu_native サブスクリーン用)。
pub(super) fn blend_normal_pm() -> wgpu::BlendState {
    wgpu::BlendState {
        color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add },
        alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add },
    }
}

/// 加算合成(premultiplied alpha、gpu_native サブスクリーン用)。
pub(super) fn blend_add_pm() -> wgpu::BlendState {
    wgpu::BlendState {
        color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add },
        alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::One, operation: wgpu::BlendOperation::Add },
    }
}

// ── パイプライン構築 ──────────────────────────────────────────────────────────

/// Normal/Add/Mul の画像スプライトパイプライン3種を1回で構築する。
/// `premultiplied` = true なら gpu_native サブスクリーン用の乗算済みアルファ版ブレンドを使う。
pub(super) fn build_image_pipeline_trio(
    device: &wgpu::Device,
    label_prefix: &str,
    shader: &wgpu::ShaderModule,
    layout: &wgpu::PipelineLayout,
    format: wgpu::TextureFormat,
    premultiplied: bool,
) -> [wgpu::RenderPipeline; 3] {
    let vbl = image_vertex_layout();
    let (normal, add) = if premultiplied {
        (blend_normal_pm(), blend_add_pm())
    } else {
        (blend_normal(), blend_add())
    };
    let mul = blend_mul();

    let build = |label: String, blend: wgpu::BlendState| {
        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label:  Some(&label),
            layout: Some(layout),
            vertex: wgpu::VertexState { module: shader, entry_point: Some("vs"), buffers: &[vbl.clone()], compilation_options: Default::default() },
            fragment: Some(wgpu::FragmentState {
                module: shader, entry_point: Some("fs"),
                targets: &[Some(wgpu::ColorTargetState { format, blend: Some(blend), write_mask: wgpu::ColorWrites::ALL })],
                compilation_options: Default::default(),
            }),
            primitive:     wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() },
            depth_stencil: None,
            multisample:   wgpu::MultisampleState::default(),
            multiview:     None,
            cache:         None,
        })
    };

    [
        build(label_prefix.to_string(), normal),
        build(format!("{label_prefix}_add"), add),
        build(format!("{label_prefix}_mul"), mul),
    ]
}

/// 単色ジオメトリ描画パイプラインを構築する。
pub(super) fn build_color_pipeline(
    device: &wgpu::Device,
    label:  &str,
    shader: &wgpu::ShaderModule,
    layout: &wgpu::PipelineLayout,
    format: wgpu::TextureFormat,
    blend:  wgpu::BlendState,
) -> wgpu::RenderPipeline {
    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
        label:  Some(label),
        layout: Some(layout),
        vertex: wgpu::VertexState { module: shader, entry_point: Some("vs"), buffers: &[color_vertex_layout()], compilation_options: Default::default() },
        fragment: Some(wgpu::FragmentState {
            module: shader, entry_point: Some("fs"),
            targets: &[Some(wgpu::ColorTargetState { format, blend: Some(blend), write_mask: wgpu::ColorWrites::ALL })],
            compilation_options: Default::default(),
        }),
        primitive:     wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() },
        depth_stencil: None,
        multisample:   wgpu::MultisampleState::default(),
        multiview:     None,
        cache:         None,
    })
}

/// Rgba8Unorm のレンダーターゲット(RENDER_ATTACHMENT + TEXTURE_BINDING + `extra_usage`)を生成する。
/// screen_texture (init / set_screen_size) と create_screen のテクスチャ生成で共有する。
pub(super) fn create_render_target(
    device: &wgpu::Device,
    label:  Option<&str>,
    w: u32, h: u32,
    extra_usage: wgpu::TextureUsages,
) -> (wgpu::Texture, wgpu::TextureView) {
    let texture = device.create_texture(&wgpu::TextureDescriptor {
        label,
        size:            wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
        mip_level_count: 1,
        sample_count:    1,
        dimension:       wgpu::TextureDimension::D2,
        format:          wgpu::TextureFormat::Rgba8Unorm,
        usage:           wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | extra_usage,
        view_formats:    &[],
    });
    let view = texture.create_view(&Default::default());
    (texture, view)
}