From ffb38a301c3203ad3600028b867b8afc892f0942 Mon Sep 17 00:00:00 2001 From: kkard2 Date: Mon, 29 May 2023 18:40:02 +0200 Subject: [PATCH 01/19] Add PNG decoding --- NoiseEngine.Native/Cargo.toml | 2 + .../interop/rendering/cpu_texture_interop.rs | 28 +++++++ .../src/interop/rendering/mod.rs | 1 + .../src/interop/result_error_kind.rs | 1 + NoiseEngine.Native/src/rendering/cpu.rs | 77 ++++++++++++++++++ .../src/rendering/encoding/mod.rs | 1 + .../src/rendering/encoding/png.rs | 67 +++++++++++++++ NoiseEngine.Native/src/rendering/mod.rs | 2 + .../Rendering/CpuTextureInteropTest.cs | 58 +++++++++++++ NoiseEngine.Tests/NoiseEngine.Tests.csproj | 10 +++ .../Resources/Textures/colors.png | Bin 0 -> 129 bytes .../Interop/Rendering/CpuTextureInterop.cs | 11 +++ NoiseEngine/Interop/ResultError.cs | 1 + NoiseEngine/Interop/ResultErrorKind.cs | 1 + NoiseEngine/Rendering/Cpu/CpuTextureData.cs | 15 ++++ NoiseEngine/Rendering/Cpu/CpuTextureFormat.cs | 10 +++ NoiseEngine/Rendering/CpuTexture.cs | 21 +++++ NoiseEngine/Rendering/CpuTexture2D.cs | 17 ++++ 18 files changed, 323 insertions(+) create mode 100644 NoiseEngine.Native/src/interop/rendering/cpu_texture_interop.rs create mode 100644 NoiseEngine.Native/src/rendering/cpu.rs create mode 100644 NoiseEngine.Native/src/rendering/encoding/mod.rs create mode 100644 NoiseEngine.Native/src/rendering/encoding/png.rs create mode 100644 NoiseEngine.Tests/Interop/Rendering/CpuTextureInteropTest.cs create mode 100644 NoiseEngine.Tests/Resources/Textures/colors.png create mode 100644 NoiseEngine/Interop/Rendering/CpuTextureInterop.cs create mode 100644 NoiseEngine/Rendering/Cpu/CpuTextureData.cs create mode 100644 NoiseEngine/Rendering/Cpu/CpuTextureFormat.cs create mode 100644 NoiseEngine/Rendering/CpuTexture.cs create mode 100644 NoiseEngine/Rendering/CpuTexture2D.cs diff --git a/NoiseEngine.Native/Cargo.toml b/NoiseEngine.Native/Cargo.toml index b56a4a4b..3d13437e 100644 --- a/NoiseEngine.Native/Cargo.toml +++ b/NoiseEngine.Native/Cargo.toml @@ -19,6 +19,8 @@ arc-cell = "0.3.3" const-utf16 = "0.2.1" cgmath = "0.18.0" crossbeam-queue = "0.3.8" +png = "0.17.8" +anyhow = "1.0.71" [dependencies.uuid] version = "1.2.2" diff --git a/NoiseEngine.Native/src/interop/rendering/cpu_texture_interop.rs b/NoiseEngine.Native/src/interop/rendering/cpu_texture_interop.rs new file mode 100644 index 00000000..8d9cfaae --- /dev/null +++ b/NoiseEngine.Native/src/interop/rendering/cpu_texture_interop.rs @@ -0,0 +1,28 @@ +use crate::{ + interop::prelude::{ + InteropReadOnlySpan, + InteropResult, + ResultError, + ResultErrorKind, + }, + rendering::{cpu::CpuTextureData, encoding} +}; + +#[no_mangle] +extern "C" fn rendering_cpu_texture_interop_decode_png( + file_data: InteropReadOnlySpan, +) -> InteropResult { + let result = encoding::png::decode(file_data.into()); + + match result { + Ok(data) => { + InteropResult::with_ok(data) + }, + Err(error) => { + InteropResult::with_err(ResultError::with_kind( + &*Box::::from(error), + ResultErrorKind::Argument, + )) + }, + } +} diff --git a/NoiseEngine.Native/src/interop/rendering/mod.rs b/NoiseEngine.Native/src/interop/rendering/mod.rs index e2b21a0e..bba2873a 100644 --- a/NoiseEngine.Native/src/interop/rendering/mod.rs +++ b/NoiseEngine.Native/src/interop/rendering/mod.rs @@ -4,3 +4,4 @@ pub mod vulkan; pub mod fence_interop; pub mod texture_interop; +pub mod cpu_texture_interop; diff --git a/NoiseEngine.Native/src/interop/result_error_kind.rs b/NoiseEngine.Native/src/interop/result_error_kind.rs index da4addf5..11cf9709 100644 --- a/NoiseEngine.Native/src/interop/result_error_kind.rs +++ b/NoiseEngine.Native/src/interop/result_error_kind.rs @@ -7,6 +7,7 @@ pub enum ResultErrorKind { NullReference = 2, InvalidOperation = 3, Overflow = 4, + Argument = 5, GraphicsUniversal = 1000, GraphicsInstanceCreate = 1001, diff --git a/NoiseEngine.Native/src/rendering/cpu.rs b/NoiseEngine.Native/src/rendering/cpu.rs new file mode 100644 index 00000000..a4de83e1 --- /dev/null +++ b/NoiseEngine.Native/src/rendering/cpu.rs @@ -0,0 +1,77 @@ +use crate::interop::prelude::InteropArray; + +#[repr(C)] +pub enum CpuTextureFormat { + R8G8B8A8, + R8G8B8, + R8, + R16G16B16A16, + R16G16B16, + R16, +} + +impl CpuTextureFormat { + pub fn pixel_size(&self) -> usize { + match self { + CpuTextureFormat::R8G8B8A8 => 4, + CpuTextureFormat::R8G8B8 => 3, + CpuTextureFormat::R8 => 1, + CpuTextureFormat::R16G16B16A16 => 8, + CpuTextureFormat::R16G16B16 => 6, + CpuTextureFormat::R16 => 2, + } + } +} + +#[repr(C)] +pub struct CpuTextureData { + extent_x: u32, + extent_y: u32, + extent_z: u32, + format: CpuTextureFormat, + data: InteropArray, +} + +impl CpuTextureData { + pub fn new( + extent_x: u32, + extent_y: u32, + extent_z: u32, + format: CpuTextureFormat, + data: InteropArray, + ) -> Self { + assert_ne!(extent_x, 0); + assert_ne!(extent_y, 0); + assert_ne!(extent_z, 0); + let size = extent_x * extent_y * extent_z * format.pixel_size() as u32; + assert_eq!(size, data.as_slice().len() as u32); + + Self { + extent_x, + extent_y, + extent_z, + format, + data, + } + } + + pub fn extent_x(&self) -> u32 { + self.extent_x + } + + pub fn extent_y(&self) -> u32 { + self.extent_y + } + + pub fn extent_z(&self) -> u32 { + self.extent_z + } + + pub fn format(&self) -> &CpuTextureFormat { + &self.format + } + + pub fn data(&self) -> &InteropArray { + &self.data + } +} diff --git a/NoiseEngine.Native/src/rendering/encoding/mod.rs b/NoiseEngine.Native/src/rendering/encoding/mod.rs new file mode 100644 index 00000000..6717f3e5 --- /dev/null +++ b/NoiseEngine.Native/src/rendering/encoding/mod.rs @@ -0,0 +1 @@ +pub mod png; diff --git a/NoiseEngine.Native/src/rendering/encoding/png.rs b/NoiseEngine.Native/src/rendering/encoding/png.rs new file mode 100644 index 00000000..d035eec6 --- /dev/null +++ b/NoiseEngine.Native/src/rendering/encoding/png.rs @@ -0,0 +1,67 @@ +use crate::{ + interop::interop_array::InteropArray, + rendering::cpu::{CpuTextureFormat, CpuTextureData}, +}; + +use anyhow::{anyhow, Result}; + +pub fn decode( + file_data: &[u8], +) -> Result { + let decoder = png::Decoder::new(file_data); + + let mut info = decoder.read_info()?; + let (mut color_type, mut bit_depth) = info.output_color_type(); + + match bit_depth { + png::BitDepth::Eight | png::BitDepth::Sixteen => { + }, + _ => { + let mut decoder = png::Decoder::new(file_data); + decoder.set_transformations( + png::Transformations::normalize_to_color8() + ); + + info = decoder.read_info()?; + (color_type, bit_depth) = info.output_color_type(); + }, + }; + + let format = match (color_type, bit_depth) { + (png::ColorType::Grayscale, png::BitDepth::Eight) => { + CpuTextureFormat::R8 + }, + (png::ColorType::Grayscale, png::BitDepth::Sixteen) => { + CpuTextureFormat::R16 + }, + (png::ColorType::GrayscaleAlpha, _) => { + return Err(anyhow!("GrayscaleAlpha is not supported")); + }, + (png::ColorType::Rgb, png::BitDepth::Eight) => { + CpuTextureFormat::R8G8B8 + }, + (png::ColorType::Rgb, png::BitDepth::Sixteen) => { + CpuTextureFormat::R16G16B16 + }, + (png::ColorType::Rgba, png::BitDepth::Eight) => { + CpuTextureFormat::R8G8B8A8 + }, + (png::ColorType::Rgba, png::BitDepth::Sixteen) => { + CpuTextureFormat::R16G16B16A16 + }, + _ => unreachable!("set_transformations should disallow other state"), + }; + + let mut data = InteropArray::new(info.output_buffer_size() as i32); + + // I think this just works? Probably? + let frame = info.next_frame(data.as_mut_slice())?; + + Ok(CpuTextureData::new( + frame.width, + frame.height, + 1, + format, + data, + )) +} diff --git a/NoiseEngine.Native/src/rendering/mod.rs b/NoiseEngine.Native/src/rendering/mod.rs index 90a85ea7..d90fdb11 100644 --- a/NoiseEngine.Native/src/rendering/mod.rs +++ b/NoiseEngine.Native/src/rendering/mod.rs @@ -4,5 +4,7 @@ pub mod presentation; pub mod vulkan; pub mod camera_clear; +pub mod cpu; +pub mod encoding; pub mod fence; pub mod texture; diff --git a/NoiseEngine.Tests/Interop/Rendering/CpuTextureInteropTest.cs b/NoiseEngine.Tests/Interop/Rendering/CpuTextureInteropTest.cs new file mode 100644 index 00000000..63ff6694 --- /dev/null +++ b/NoiseEngine.Tests/Interop/Rendering/CpuTextureInteropTest.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NoiseEngine.Interop; +using NoiseEngine.Interop.Rendering; +using NoiseEngine.Mathematics; +using NoiseEngine.Rendering; +using NoiseEngine.Rendering.Cpu; + +namespace NoiseEngine.Tests.Interop.Rendering; + +public class CpuTextureInteropTest { + + private readonly Dictionary, Color32> textureColors = new Dictionary, Color32> { + { new Vector2(0, 0), new Color32(255, 0, 0) }, + { new Vector2(1, 0), new Color32(0, 255, 0) }, + { new Vector2(2, 0), new Color32(0, 0, 0) }, + { new Vector2(0, 1), new Color32(0, 0, 255) }, + { new Vector2(1, 1), new Color32(255, 255, 0) }, + { new Vector2(2, 1), new Color32(255, 0, 255) } + }; + + [Fact] + public void DecodePng() { + byte[] fileData = File.ReadAllBytes("./Resources/Textures/colors.png"); + InteropResult result = CpuTextureInterop.DecodePng(fileData); + + Assert.True(result.IsOk); + CpuTextureData data = result.Value; + + Assert.Equal(3, data.ExtentX); + Assert.Equal(2, data.ExtentY); + Assert.Equal(1, data.ExtentZ); + Assert.Equal(CpuTextureFormat.R8G8B8, data.Format); + + byte[] expected = GetColorDataRgb(textureColors, new Vector2(data.ExtentX, data.ExtentY)); + + Assert.Equal(expected, data.Data.ToArray()); + } + + private static byte[] GetColorDataRgb(IDictionary, Color32> colors, Vector2 size) { + byte[] result = new byte[size.X * size.Y * 3]; + + int i = 0; + + for (uint y = 0; y < size.Y; y++) { + for (uint x = 0; x < size.X; x++) { + result[i + 0] = colors[new Vector2(x, y)].R; + result[i + 1] = colors[new Vector2(x, y)].G; + result[i + 2] = colors[new Vector2(x, y)].B; + i += 3; + } + } + + return result; + } + +} diff --git a/NoiseEngine.Tests/NoiseEngine.Tests.csproj b/NoiseEngine.Tests/NoiseEngine.Tests.csproj index 2f216e53..664c2cb7 100644 --- a/NoiseEngine.Tests/NoiseEngine.Tests.csproj +++ b/NoiseEngine.Tests/NoiseEngine.Tests.csproj @@ -33,6 +33,16 @@ + + + + + + + PreserveNewest + + +