From f70917c8aae0d184ed7ce5e63e6347fdc21921e3 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Fri, 17 Jul 2026 18:43:05 -0700 Subject: load a texture image also refactor buffer management a little this doesn't yet draw the texture Change-Id: Id2efa761223a588bf599041ac525c3b988f0113b Force-Push: yes --- Cargo.lock | 64 ++++++++ Cargo.toml | 10 +- build.rs | 32 +++- src/error.rs | 25 +++ src/graphics_window_dressing.rs | 317 ++++++++++++++++++++++++++++++++----- src/linear_algebra.rs | 3 +- textures/forest_leaves_04_diff.png | Bin 0 -> 5925987 bytes 7 files changed, 398 insertions(+), 53 deletions(-) create mode 100644 textures/forest_leaves_04_diff.png diff --git a/Cargo.lock b/Cargo.lock index f370fb5..9523c82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -297,6 +303,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -352,12 +367,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -617,6 +651,16 @@ dependencies = [ "paste", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "ndk" version = "0.9.0" @@ -956,6 +1000,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -1146,6 +1203,12 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.1.1" @@ -1219,6 +1282,7 @@ name = "surreality" version = "0.1.0" dependencies = [ "libloading", + "png", "vulkanalia", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index 684023d..1f98cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,17 +10,9 @@ vulkan-validation = [ ] [dependencies] libloading = "0.8.9" +png = "0.18.1" winit = "0.30.13" [dependencies.vulkanalia] version = "0.35.0" features = [ "libloading", "window" ] -#anyhow = "1" -#log = "0.4" -#cgmath = "0.18" -#png = "0.17" -#pretty_env_logger = "0.5" -#thiserror = "1" -#tobj = { version = "3", features = ["log"] } -#vulkanalia = { version = "=0.35.0", features = ["libloading", "provisional", "window"] } -#winit = "0.29" diff --git a/build.rs b/build.rs index b4ffa72..7399346 100644 --- a/build.rs +++ b/build.rs @@ -10,7 +10,16 @@ fn main() { } } -fn process_all () -> std::io::Result<()> { + +fn process_all() -> std::io::Result<()> { + process_shaders()?; + process_textures()?; + + Ok(()) +} + + +fn process_shaders() -> std::io::Result<()> { let out_dir = env::var_os("OUT_DIR").unwrap(); for input in fs::read_dir("shaders")? { @@ -51,3 +60,24 @@ fn process_all () -> std::io::Result<()> { Ok(()) } + +fn process_textures() -> std::io::Result<()> { + for input in fs::read_dir("textures")? { + let input_path = input?.path(); + if !input_path.is_file() { + continue; + } + + if let Some(Some(filename)) = input_path.file_name() + .map(|name| name.to_str()) + && filename.get(.. 1) == Some(".") + { + continue; + } + + println!("cargo::rerun-if-changed={}", input_path.display()); + } + + Ok(()) +} + diff --git a/src/error.rs b/src/error.rs index f034683..729ff23 100644 --- a/src/error.rs +++ b/src/error.rs @@ -90,6 +90,31 @@ impl From for Error { } } +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error { + message: format!("System I/O error: {}", e), + } + } +} + +impl From for Error { + fn from(e: png::DecodingError) -> Self { + match e { + png::DecodingError::IoError(e) => Self::from(e), + png::DecodingError::Format(_) => Error { + message: "Texture image is not a valid PNG.".to_string(), + }, + png::DecodingError::Parameter(e) => Error { + message: format!("PNG library misusage: {}", e), + }, + png::DecodingError::LimitsExceeded => Error { + message: "PNG needed too much memory to decode.".to_string(), + } + } + } +} + pub fn ignore_errors(mut body: impl FnMut() -> Result<()>) -> () { if let Err(e) = body() { eprintln!("Error: {}", e); diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs index 38d4188..4dddb35 100644 --- a/src/graphics_window_dressing.rs +++ b/src/graphics_window_dressing.rs @@ -6,7 +6,10 @@ use crate::graphics_permanent::{ use crate::linear_algebra::{ Vertex, VERTICES, INDICES, UniformBlock }; use std::collections::BTreeSet; +use std::io::Cursor; use std::ptr::copy_nonoverlapping; + +use png::Decoder; use vulkanalia::{ Device, Instance }; use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, DeviceV1_0, KhrSwapchainExtensionDeviceCommands }; @@ -43,6 +46,9 @@ pub struct WindowDressing { index_buffer: vk::Buffer, index_buffer_memory: vk::DeviceMemory, + texture_image: vk::Image, + texture_image_memory: vk::DeviceMemory, + uniform_buffers: Vec, pub uniform_buffer_memory: Vec, @@ -124,6 +130,11 @@ impl WindowDressing { let (index_buffer, index_buffer_memory) = init_index_buffer(instance, physical_device, device, graphics_queue, &transient_command_pool)?; + + let (texture_image, texture_image_memory) + = init_texture(instance, physical_device, device, + graphics_queue, &transient_command_pool)?; + let (uniform_buffers, uniform_buffer_memory) = init_uniform_buffers(instance, physical_device, device, swapchain.images.len())?; @@ -152,6 +163,8 @@ impl WindowDressing { vertex_buffer_memory, index_buffer, index_buffer_memory, + texture_image, + texture_image_memory, uniform_buffers, uniform_buffer_memory, descriptor_pool, @@ -241,6 +254,8 @@ impl WindowDressing { unsafe { device.free_memory(self.vertex_buffer_memory, None) }; unsafe { device.destroy_buffer(self.index_buffer, None) }; unsafe { device.free_memory(self.index_buffer_memory, None) }; + unsafe { device.destroy_image(self.texture_image, None) }; + unsafe { device.free_memory(self.texture_image_memory, None) }; for semaphore in self.concurrency.image_available_semaphores { unsafe { device.destroy_semaphore(semaphore, None) }; @@ -629,6 +644,54 @@ fn init_index_buffer(instance: &Instance, vk::BufferUsageFlags::INDEX_BUFFER, INDICES) } + +#[allow(unsafe_code)] +fn init_texture(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + queue: &vk::Queue, command_pool: &vk::CommandPool) + -> Result<(vk::Image, vk::DeviceMemory)> +{ + let png = include_bytes!("../textures/forest_leaves_04_diff.png"); + + let decoder = Decoder::new(Cursor::new(png)); + let mut reader = decoder.read_info()?; + + let (width, height) = reader.info().size(); + + let mut pixels = vec![0; reader.info().raw_bytes()]; + reader.next_frame(&mut pixels)?; + + let (staging_buffer, staging_memory, byte_size) + = stage_in_buffer(instance, physical_device, device, &pixels)?; + + let (image, image_memory) + = allocate_image(instance, physical_device, device, width, height, + vk::Format::R8G8B8A8_SRGB, + vk::ImageTiling::OPTIMAL, + vk::ImageUsageFlags::SAMPLED + | vk::ImageUsageFlags::TRANSFER_DST, + vk::MemoryPropertyFlags::DEVICE_LOCAL)?; + + change_image_layout(device, queue, command_pool, &image, + vk::Format::R8G8B8A8_SRGB, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL)?; + + copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image, + width, height)?; + + change_image_layout(device, queue, command_pool, &image, + vk::Format::R8G8B8A8_SRGB, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)?; + + unsafe { device.destroy_buffer(staging_buffer, None) }; + unsafe { device.free_memory(staging_memory, None) }; + + Ok((image, image_memory)) +} + + fn init_uniform_buffers(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, count: usize) @@ -640,7 +703,7 @@ fn init_uniform_buffers(instance: &Instance, for _ in 0 .. count { let (buffer, memory) = allocate_buffer( instance, physical_device, device, - size_of::>() as u64, + size_of::>() as vk::DeviceSize, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_COHERENT | vk::MemoryPropertyFlags::HOST_VISIBLE)?; @@ -659,33 +722,18 @@ fn init_buffer(instance: &Instance, usage: vk::BufferUsageFlags, contents: &[T]) -> Result<(vk::Buffer, vk::DeviceMemory)> { - let size = (size_of::() * contents.len()) as u64; - - let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC; - let staging_memory_flags = vk::MemoryPropertyFlags::HOST_COHERENT - | vk::MemoryPropertyFlags::HOST_VISIBLE; - let (staging_buffer, staging_memory) - = allocate_buffer(instance, physical_device, device, - size, staging_usage, staging_memory_flags)?; - - let host_memory = unsafe { - device.map_memory(staging_memory, 0, size, vk::MemoryMapFlags::empty()) - }?; - - unsafe { - copy_nonoverlapping(contents.as_ptr(), host_memory.cast(), contents.len()) - }; - - unsafe { device.unmap_memory(staging_memory) }; + let (staging_buffer, staging_memory, size) + = stage_in_buffer(instance, physical_device, device, contents)?; let final_usage = vk::BufferUsageFlags::TRANSFER_DST | usage; let final_memory_flags = vk::MemoryPropertyFlags::DEVICE_LOCAL; let (final_buffer, device_memory) = allocate_buffer(instance, physical_device, device, - size, final_usage, final_memory_flags)?; + size as vk::DeviceSize, final_usage, + final_memory_flags)?; - copy_buffer(device, queue, command_pool, - &staging_buffer, &final_buffer, size)?; + copy_buffer(device, queue, command_pool, &staging_buffer, &final_buffer, + size as vk::DeviceSize)?; unsafe { device.destroy_buffer(staging_buffer, None) }; unsafe { device.free_memory(staging_memory, None) }; @@ -728,7 +776,7 @@ fn init_descriptor_sets(device: &Device, layout: &vk::DescriptorSetLayout, let buffer_info = vk::DescriptorBufferInfo::builder() .buffer(buffers[index]) .offset(0) - .range(size_of::>() as u64); + .range(size_of::>() as vk::DeviceSize); let buffer_info_list = [buffer_info]; let descriptor_write_info = vk::WriteDescriptorSet::builder() @@ -985,6 +1033,37 @@ fn pick_memory_type(instance: &Instance, } +#[allow(unsafe_code)] +fn stage_in_buffer(instance: &Instance, + physical_device: &vk::PhysicalDevice, device: &Device, + contents: &[T]) + -> Result<(vk::Buffer, vk::DeviceMemory, usize)> +{ + let size = size_of::() * contents.len(); + + let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC; + let staging_memory_flags = vk::MemoryPropertyFlags::HOST_COHERENT + | vk::MemoryPropertyFlags::HOST_VISIBLE; + let (staging_buffer, staging_memory) + = allocate_buffer(instance, physical_device, device, + size as vk::DeviceSize, staging_usage, + staging_memory_flags)?; + + let host_memory = unsafe { + device.map_memory(staging_memory, 0, size as vk::DeviceSize, + vk::MemoryMapFlags::empty()) + }?; + + unsafe { + copy_nonoverlapping(contents.as_ptr(), host_memory.cast(), contents.len()) + }; + + unsafe { device.unmap_memory(staging_memory) }; + + Ok((staging_buffer, staging_memory, size)) +} + + #[allow(unsafe_code)] fn allocate_buffer(instance: &Instance, physical_device: &vk::PhysicalDevice, device: &Device, @@ -1008,7 +1087,6 @@ fn allocate_buffer(instance: &Instance, let type_index = pick_memory_type(instance, physical_device, &memory_flags, &requirements)?; - let memory_info = vk::MemoryAllocateInfo::builder() .allocation_size(requirements.size) .memory_type_index(type_index); @@ -1023,10 +1101,168 @@ fn allocate_buffer(instance: &Instance, #[allow(unsafe_code)] fn copy_buffer(device: &Device, queue: &vk::Queue, - command_pool: &vk::CommandPool, - source: &vk::Buffer, destination: &vk::Buffer, - size: vk::DeviceSize) + command_pool: &vk::CommandPool, source: &vk::Buffer, + destination: &vk::Buffer, size: vk::DeviceSize) -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let copy_info = vk::BufferCopy::builder().size(size); + unsafe { + device.cmd_copy_buffer(command_buffer, *source, *destination, + &[copy_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn allocate_image(instance: &Instance, physical_device: &vk::PhysicalDevice, + device: &Device, width: u32, height: u32, + format: vk::Format, tiling: vk::ImageTiling, + usage: vk::ImageUsageFlags, + memory_flags: vk::MemoryPropertyFlags) + -> Result<(vk::Image, vk::DeviceMemory)> +{ + let image_info = vk::ImageCreateInfo::builder() + .image_type(vk::ImageType::_2D) + .extent(vk::Extent3D { width, height, depth: 1 }) + .mip_levels(1) + .array_layers(1) + .format(format) + .tiling(tiling) + .initial_layout(vk::ImageLayout::UNDEFINED) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .samples(vk::SampleCountFlags::_1) + .flags(vk::ImageCreateFlags::empty()); + let image = unsafe { device.create_image(&image_info, None) }?; + + let requirements = unsafe { device.get_image_memory_requirements(image) }; + + let type_index = pick_memory_type(instance, physical_device, + &vk::MemoryPropertyFlags::DEVICE_LOCAL, + &requirements)?; + + let image_memory_info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(type_index); + let image_memory = unsafe { + device.allocate_memory(&image_memory_info, None) + }?; + + unsafe { device.bind_image_memory(image, image_memory, 0) }?; + + Ok((image, image_memory)) +} + + +#[allow(unsafe_code)] +fn copy_buffer_to_image(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, source: &vk::Buffer, + destination: &vk::Image, width: u32, height: u32) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_layers = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1); + + let copy_info = vk::BufferImageCopy::builder() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(subresource_layers) + .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) + .image_extent(vk::Extent3D { width, height, depth: 1 }); + + unsafe { + device.cmd_copy_buffer_to_image(command_buffer, *source, *destination, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[copy_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn change_image_layout(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, image: &vk::Image, + format: vk::Format, old: vk::ImageLayout, + new: vk::ImageLayout) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + // Notionally this is a property that our caller is in a better position + // to know than we are, but in practice the nature of the transition + // strongly implies a particular phase of the image's lifecycle, so we just + // compute it here. + let (source_access, source_stage, destination_access, destination_stage) + = match (old, new) + { + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) + => (vk::AccessFlags::empty(), + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER), + (vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + => (vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::FRAGMENT_SHADER), + _ => return Err(Error { + message: + format!("Don't know how to change from image layout {:?} to {:?}", + old, new) + }) + }; + + let barrier_info = vk::ImageMemoryBarrier::builder() + .image(*image) + .subresource_range(subresource_range) + .old_layout(old) + .new_layout(new) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .src_access_mask(source_access) + .dst_access_mask(destination_access); + + unsafe { + device.cmd_pipeline_barrier(command_buffer, + source_stage, destination_stage, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[barrier_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +#[allow(unsafe_code)] +fn begin_transient_commands(device: &Device, command_pool: &vk::CommandPool) + -> Result { let command_buffer_allocation_info = vk::CommandBufferAllocateInfo::builder() @@ -1044,29 +1280,26 @@ fn copy_buffer(device: &Device, queue: &vk::Queue, device.begin_command_buffer(command_buffer, &command_buffer_begin_info) }?; - let copy_info = vk::BufferCopy::builder().size(size); - unsafe { - device.cmd_copy_buffer(command_buffer, *source, *destination, - &[copy_info]) - }; + Ok(command_buffer) +} + +#[allow(unsafe_code)] +fn end_transient_commands(command_buffer: vk::CommandBuffer, + device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool) + -> Result<()> +{ unsafe { device.end_command_buffer(command_buffer) }?; let command_buffers = [command_buffer]; - let submit_info = vk::SubmitInfo::builder() .command_buffers(&command_buffers); - unsafe { - device.queue_submit(*queue, &[submit_info], vk::Fence::null()) - }?; + unsafe { device.queue_submit(*queue, &[submit_info], vk::Fence::null()) }?; - unsafe { - device.queue_wait_idle(*queue) - }?; + unsafe { device.queue_wait_idle(*queue) }?; - unsafe { - device.free_command_buffers(*command_pool, &command_buffers) - }; + unsafe { device.free_command_buffers(*command_pool, &command_buffers) }; Ok(()) } diff --git a/src/linear_algebra.rs b/src/linear_algebra.rs index eb9aca2..0051fa8 100644 --- a/src/linear_algebra.rs +++ b/src/linear_algebra.rs @@ -434,7 +434,8 @@ impl Mat4 { // internally to the graphics API. This is necessary in order to produce the // homogenous behavior of the z-coordinate; it also acts as a convenient way // to scale the x and y coordinates by depth, implementing what artists call - // foreshortening. + // foreshortening. We will need to talk about this final post-processing + // step at length, so we call it "w-division" for convenience. // // The field of view is in radians, and is used as the angle measured // vertically from top to bottom of the screen. The computed y scale will be diff --git a/textures/forest_leaves_04_diff.png b/textures/forest_leaves_04_diff.png new file mode 100644 index 0000000..a87b2e6 Binary files /dev/null and b/textures/forest_leaves_04_diff.png differ -- cgit 1.4.1