use std::collections::HashMap;
use crate::{GpuContextHandle, GpuError, GpuImageHandle, Payload};
use image::DynamicImage;
use super::ShaderContext;
pub struct ShaderRunOutput {
pub buffers: HashMap<u32, Vec<u8>>,
pub textures: HashMap<u32, GpuImageHandle>,
}
impl ShaderRunOutput {
pub fn texture_rgba8(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<image::ImageBuffer<image::Rgba<u8>, Vec<u8>>> {
self.buffers.get(&binding).and_then(|bytes| {
image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(width, height, bytes.clone())
})
}
pub fn texture_rgba8_image(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<DynamicImage> {
self.texture_rgba8(binding, width, height)
.map(DynamicImage::ImageRgba8)
}
pub fn texture_r32f(&self, binding: u32, width: u32, height: u32) -> Option<Vec<f32>> {
self.buffers.get(&binding).and_then(|bytes| {
let expected = (width as usize) * (height as usize) * 4;
if bytes.len() < expected || bytes.len() % 4 != 0 {
return None;
}
let mut out = Vec::with_capacity(bytes.len() / 4);
for chunk in bytes.chunks_exact(4) {
out.push(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
Some(out)
})
}
pub fn texture_r32f_gray_u8(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<image::ImageBuffer<image::Luma<u8>, Vec<u8>>> {
self.texture_r32f(binding, width, height).and_then(|vals| {
if vals.len() < (width as usize) * (height as usize) {
return None;
}
let mut out = Vec::with_capacity(vals.len());
for v in vals {
let clamped = v.clamp(0.0, 1.0);
out.push((clamped * 255.0).round().clamp(0.0, 255.0) as u8);
}
image::ImageBuffer::<image::Luma<u8>, _>::from_raw(width, height, out)
})
}
pub fn texture_r32f_image(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<image::ImageBuffer<image::Luma<f32>, Vec<f32>>> {
self.texture_r32f(binding, width, height).and_then(|vals| {
image::ImageBuffer::<image::Luma<f32>, _>::from_raw(width, height, vals)
})
}
pub fn texture_rgba16f(&self, binding: u32, width: u32, height: u32) -> Option<Vec<f32>> {
self.buffers.get(&binding).and_then(|bytes| {
if bytes.len() < (width as usize) * (height as usize) * 8 {
return None;
}
let mut out = Vec::with_capacity((width as usize) * (height as usize) * 4);
for chunk in bytes.chunks_exact(2) {
let half = u16::from_le_bytes([chunk[0], chunk[1]]);
let f = half::f16::from_bits(half).to_f32();
out.push(f);
}
Some(out)
})
}
pub fn texture_rgba16f_image(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<image::ImageBuffer<image::Rgba<f32>, Vec<f32>>> {
self.texture_rgba16f(binding, width, height)
.and_then(|vals| {
image::ImageBuffer::<image::Rgba<f32>, _>::from_raw(width, height, vals)
})
}
pub fn storage_rgba8_image(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<DynamicImage> {
self.texture_rgba8_image(binding, width, height)
}
pub fn storage_r32f(&self, binding: u32, width: u32, height: u32) -> Option<Vec<f32>> {
self.texture_r32f(binding, width, height)
}
pub fn storage_rgba16f_image(
&self,
binding: u32,
width: u32,
height: u32,
) -> Option<image::ImageBuffer<image::Rgba<f32>, Vec<f32>>> {
self.texture_rgba16f_image(binding, width, height)
}
pub fn texture_handle(&self, binding: u32) -> Option<GpuImageHandle> {
self.textures.get(&binding).cloned()
}
pub fn into_payload(
&self,
binding: u32,
gpu: Option<&GpuContextHandle>,
width: u32,
height: u32,
) -> Result<Payload<DynamicImage>, GpuError> {
if let Some(handle) = self.texture_handle(binding) {
return Ok(Payload::Gpu(handle));
}
let img = self
.texture_rgba8(binding, width, height)
.ok_or_else(|| GpuError::Internal("missing texture output".into()))?;
Payload::<DynamicImage>::from_rgba_bytes(gpu, img.into_raw(), width, height)
.map_err(|e| GpuError::Internal(e.to_string()))
}
pub fn into_payload_with_ctx(
&self,
binding: u32,
ctx: &ShaderContext,
width: u32,
height: u32,
) -> Result<Payload<DynamicImage>, GpuError> {
self.into_payload(binding, ctx.gpu.as_ref(), width, height)
}
}