}
}
+struct VulkanTouchedSwapchain {
+ image: vk::Image,
+ layout: vk::ImageLayout,
+ access_mask: vk::AccessFlags2,
+ stage_mask: vk::PipelineStageFlags2,
+}
+
struct VulkanCmdEncoder {
#[cfg(debug_assertions)]
in_render_pass: bool,
command_buffer: vk::CommandBuffer,
bound_pipeline: Option<VulkanBoundPipeline>,
- swapchains_touched: HashMap<vk::SurfaceKHR, (vk::Image, vk::PipelineStageFlags2)>,
+ swapchains_touched: HashMap<vk::SurfaceKHR, VulkanTouchedSwapchain>,
}
impl Default for VulkanCmdEncoder {
}
}
+ fn cmd_compute_touch_swapchain(&self, cmd_encoder: &mut CmdEncoder, image: Image) {
+ let cmd_encoder = self.cmd_encoder_mut(cmd_encoder);
+
+ match self.image_pool.lock().get(image.0) {
+ Some(VulkanImageHolder::Swapchain(image)) => {
+ assert!(
+ !cmd_encoder.swapchains_touched.contains_key(&image.surface),
+ "swapchain attached multiple times in a command buffer"
+ );
+ cmd_encoder.swapchains_touched.insert(
+ image.surface,
+ VulkanTouchedSwapchain {
+ image: image.image,
+ layout: vk::ImageLayout::General,
+ access_mask: vk::AccessFlags2::SHADER_STORAGE_WRITE,
+ stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+ },
+ );
+
+ // Transition swapchain image to shader storage write
+ let image_memory_barriers = &[vk::ImageMemoryBarrier2 {
+ src_stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+ src_access_mask: vk::AccessFlags2::NONE,
+ dst_stage_mask: vk::PipelineStageFlags2::COMPUTE_SHADER,
+ dst_access_mask: vk::AccessFlags2::SHADER_STORAGE_WRITE,
+ src_queue_family_index: self.universal_queue_family_index,
+ dst_queue_family_index: self.universal_queue_family_index,
+ old_layout: vk::ImageLayout::Undefined,
+ new_layout: vk::ImageLayout::General,
+ image: image.image,
+ subresource_range: vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: !0,
+ base_array_layer: 0,
+ layer_count: !0,
+ },
+ ..default()
+ }];
+
+ let dependency_info = vk::DependencyInfo {
+ image_memory_barriers: image_memory_barriers.into(),
+ ..default()
+ };
+
+ unsafe {
+ self.device_fn
+ .cmd_pipeline_barrier2(cmd_encoder.command_buffer, &dependency_info)
+ };
+ }
+ _ => panic!(),
+ }
+ }
+
fn cmd_barrier(
&self,
cmd_encoder: &mut CmdEncoder,
..default()
}
}
- TypedBind::Image(images) => {
+ TypedBind::SampledImage(images) => {
let image_infos_iter = images.iter().map(|(image_layout, image)| {
let image_view = self.image_pool.lock().get(image.0).unwrap().image_view();
vk::DescriptorImageInfo {
..default()
}
}
+ TypedBind::StorageImage(images) => {
+ let image_infos_iter = images.iter().map(|(image_layout, image)| {
+ let image_view = self.image_pool.lock().get(image.0).unwrap().image_view();
+ vk::DescriptorImageInfo {
+ image_layout: match image_layout {
+ ImageLayout::Optimal => vk::ImageLayout::ReadOnlyOptimal,
+ ImageLayout::General => vk::ImageLayout::General,
+ },
+ image_view,
+ sampler: vk::Sampler::null(),
+ }
+ });
+ let image_infos = arena.alloc_slice_fill_iter(image_infos_iter);
+ vk::WriteDescriptorSet {
+ dst_set: descriptor_set,
+ dst_binding: bind.binding,
+ dst_array_element: bind.array_element,
+ descriptor_count: image_infos.len() as u32,
+ descriptor_type: vk::DescriptorType::StorageImage,
+ image_info: image_infos.as_ptr(),
+ ..default()
+ }
+ }
TypedBind::UniformBuffer(buffers) => {
let buffer_infos_iter = buffers.iter().map(|buffer_arg| {
let (buffer, offset, range) = self.unwrap_buffer_arg(buffer_arg);
);
cmd_encoder.swapchains_touched.insert(
image.surface,
- (
- image.image,
- vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
- ),
+ VulkanTouchedSwapchain {
+ image: image.image,
+ layout: vk::ImageLayout::AttachmentOptimal,
+ access_mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
+ stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
+ },
);
// transition swapchain image to attachment optimal
#[cfg(debug_assertions)]
debug_assert!(!cmd_encoder.in_render_pass);
- for &(image, _) in cmd_encoder.swapchains_touched.values() {
+ for &VulkanTouchedSwapchain {
+ image,
+ layout,
+ access_mask,
+ stage_mask,
+ } in cmd_encoder.swapchains_touched.values()
+ {
// transition swapchain image from attachment optimal to present src
let image_memory_barriers = &[vk::ImageMemoryBarrier2 {
- src_stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
- src_access_mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
- dst_stage_mask: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
+ src_stage_mask: stage_mask,
+ src_access_mask: access_mask,
+ dst_stage_mask: stage_mask,
dst_access_mask: vk::AccessFlags2::NONE,
src_queue_family_index: self.universal_queue_family_index,
dst_queue_family_index: self.universal_queue_family_index,
- old_layout: vk::ImageLayout::AttachmentOptimal,
+ old_layout: layout,
new_layout: vk::ImageLayout::PresentSrcKhr,
image,
subresource_range: vk::ImageSubresourceRange {
let mut signal_semaphores = Vec::new();
if !cmd_encoder.swapchains_touched.is_empty() {
- for (surface, (_, stage_mask)) in cmd_encoder.swapchains_touched.drain() {
+ for (
+ surface,
+ VulkanTouchedSwapchain {
+ image: _,
+ layout: _,
+ access_mask: _,
+ stage_mask,
+ },
+ ) in cmd_encoder.swapchains_touched.drain()
+ {
self.touch_swapchain(
frame,
surface,
use std::path::Path;
use std::time::{Duration, Instant};
+use narcissus_core::dds;
use renderdoc_sys as rdoc;
use fonts::{FontFamily, Fonts};
use narcissus_gpu::{
create_device, Access, Bind, BufferImageCopy, BufferUsageFlags, ClearValue, CmdEncoder, Device,
DeviceExt, Extent2d, Extent3d, Frame, Image, ImageAspectFlags, ImageBarrier, ImageDesc,
- ImageDimension, ImageFormat, ImageLayout, ImageTiling, ImageUsageFlags, IndexType, LoadOp,
- MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment, RenderingDesc, Scissor,
- StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport,
+ ImageDimension, ImageFormat, ImageLayout, ImageSubresourceRange, ImageTiling, ImageUsageFlags,
+ IndexType, LoadOp, MemoryLocation, Offset2d, PersistentBuffer, RenderingAttachment,
+ RenderingDesc, Scissor, StoreOp, SwapchainImage, ThreadToken, TypedBind, Viewport,
};
use narcissus_image as image;
use narcissus_maths::{
clamp, perlin_noise3, sin_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4, Point3, Vec3,
};
-use pipelines::{BasicPipeline, BasicUniforms, PrimitiveInstance, PrimitiveVertex, UiPipeline};
+use pipelines::{
+ BasicPipeline, BasicUniforms, DisplayTransformPipeline, PrimitiveInstance, PrimitiveVertex,
+ UiPipeline,
+};
use spring::simple_spring_damper_exact;
mod fonts;
}
}
-struct Model<'device> {
+struct Model<'gpu> {
indices: u32,
- vertex_buffer: PersistentBuffer<'device>,
- index_buffer: PersistentBuffer<'device>,
+ vertex_buffer: PersistentBuffer<'gpu>,
+ index_buffer: PersistentBuffer<'gpu>,
}
enum ModelRes {
const MAX_MODELS: usize = 1;
}
-struct Models<'device>([Model<'device>; ModelRes::MAX_MODELS]);
+struct Models<'gpu>([Model<'gpu>; ModelRes::MAX_MODELS]);
enum ImageRes {
+ TonyMcMapfaceLut,
Shark,
}
impl ImageRes {
- const MAX_IMAGES: usize = 1;
+ const MAX_IMAGES: usize = 2;
}
struct Images([Image; ImageRes::MAX_IMAGES]);
type Gpu = dyn Device + 'static;
-struct DrawState<'device> {
- gpu: &'device Gpu,
+struct DrawState<'gpu> {
+ gpu: &'gpu Gpu,
basic_pipeline: BasicPipeline,
ui_pipeline: UiPipeline,
+ display_transform_pipeline: DisplayTransformPipeline,
width: u32,
height: u32,
depth_image: Image,
+ render_target_image: Image,
+
glyph_atlas_image: Image,
- models: Models<'device>,
+ models: Models<'gpu>,
images: Images,
transforms: Vec<Affine3>,
image
}
+ fn load_dds<P>(
+ gpu: &Gpu,
+ frame: &Frame,
+ thread_token: &ThreadToken,
+ cmd_encoder: &mut CmdEncoder,
+ path: P,
+ ) -> Image
+ where
+ P: AsRef<Path>,
+ {
+ let image_data = std::fs::read(path.as_ref()).unwrap();
+ let dds = dds::Dds::from_buffer(&image_data).unwrap();
+ let header_dxt10 = dds.header_dxt10.unwrap();
+
+ let width = dds.header.width;
+ let height = dds.header.height;
+ let depth = dds.header.depth;
+
+ let dimension = match header_dxt10.resource_dimension {
+ dds::D3D10ResourceDimension::Texture1d => ImageDimension::Type1d,
+ dds::D3D10ResourceDimension::Texture2d => ImageDimension::Type2d,
+ dds::D3D10ResourceDimension::Texture3d => ImageDimension::Type3d,
+ _ => panic!(),
+ };
+
+ let format = match header_dxt10.dxgi_format {
+ dds::DxgiFormat::R9G9B9E5_SHAREDEXP => ImageFormat::E5B9G9R9_UFLOAT,
+ _ => panic!(),
+ };
+
+ let image = gpu.create_image(&ImageDesc {
+ memory_location: MemoryLocation::Device,
+ host_mapped: false,
+ usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
+ dimension,
+ format,
+ tiling: ImageTiling::Optimal,
+ width,
+ height,
+ depth,
+ layer_count: 1,
+ mip_levels: 1,
+ });
+
+ gpu.cmd_barrier(
+ cmd_encoder,
+ None,
+ &[ImageBarrier {
+ prev_access: &[Access::None],
+ next_access: &[Access::TransferWrite],
+ prev_layout: ImageLayout::Optimal,
+ next_layout: ImageLayout::Optimal,
+ subresource_range: ImageSubresourceRange::default(),
+ image,
+ }],
+ );
+
+ let buffer = gpu.request_transient_buffer_with_data(
+ frame,
+ thread_token,
+ BufferUsageFlags::TRANSFER,
+ dds.data,
+ );
+
+ gpu.cmd_copy_buffer_to_image(
+ cmd_encoder,
+ buffer.to_arg(),
+ image,
+ ImageLayout::Optimal,
+ &[BufferImageCopy {
+ image_extent: Extent3d {
+ width,
+ height,
+ depth,
+ },
+ ..default()
+ }],
+ );
+
+ gpu.cmd_barrier(
+ cmd_encoder,
+ None,
+ &[ImageBarrier {
+ prev_access: &[Access::TransferWrite],
+ next_access: &[Access::ShaderSampledImageRead],
+ prev_layout: ImageLayout::Optimal,
+ next_layout: ImageLayout::Optimal,
+ subresource_range: ImageSubresourceRange::default(),
+ image,
+ }],
+ );
+
+ image
+ }
+
let images;
let frame = gpu.begin_frame();
{
{
let cmd_encoder = &mut cmd_encoder;
- images = Images([load_image(
- gpu,
- frame,
- thread_token,
- cmd_encoder,
- "title/shark/data/blåhaj.png",
- )]);
+ images = Images([
+ load_dds(
+ gpu,
+ frame,
+ thread_token,
+ cmd_encoder,
+ "title/shark/data/tony_mc_mapface.dds",
+ ),
+ load_image(
+ gpu,
+ frame,
+ thread_token,
+ cmd_encoder,
+ "title/shark/data/blåhaj.png",
+ ),
+ ]);
}
gpu.submit(frame, cmd_encoder);
images
}
-impl<'device> DrawState<'device> {
- fn new(gpu: &'device Gpu, thread_token: &ThreadToken) -> Self {
+impl<'gpu> DrawState<'gpu> {
+ fn new(gpu: &'gpu Gpu, thread_token: &ThreadToken) -> Self {
let basic_pipeline = BasicPipeline::new(gpu);
let ui_pipeline = UiPipeline::new(gpu);
+ let primitive_pipeline = DisplayTransformPipeline::new(gpu);
let models = load_models(gpu);
let images = load_images(gpu, thread_token);
gpu,
basic_pipeline,
ui_pipeline,
+ display_transform_pipeline: primitive_pipeline,
width: 0,
height: 0,
depth_image: default(),
+ render_target_image: default(),
glyph_atlas_image: default(),
models,
images,
);
let clip_from_model = clip_from_camera * camera_from_model;
+ let atlas_width = ui_state.glyph_cache.width() as u32;
+ let atlas_height = ui_state.glyph_cache.height() as u32;
+
let mut cmd_encoder = self.gpu.request_cmd_encoder(frame, thread_token);
{
let cmd_encoder = &mut cmd_encoder;
- if width != self.width || height != self.height {
- gpu.destroy_image(frame, self.depth_image);
- self.depth_image = gpu.create_image(&ImageDesc {
+ if self.glyph_atlas_image.is_null() {
+ self.glyph_atlas_image = gpu.create_image(&ImageDesc {
memory_location: MemoryLocation::Device,
host_mapped: false,
- usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
+ usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
dimension: ImageDimension::Type2d,
- format: ImageFormat::DEPTH_F32,
+ format: ImageFormat::R8_SRGB,
tiling: ImageTiling::Optimal,
- width,
- height,
+ width: atlas_width,
+ height: atlas_height,
depth: 1,
layer_count: 1,
mip_levels: 1,
None,
&[ImageBarrier::layout_optimal(
&[Access::None],
- &[Access::DepthStencilAttachmentWrite],
- self.depth_image,
- ImageAspectFlags::DEPTH,
+ &[Access::FragmentShaderSampledImageRead],
+ self.glyph_atlas_image,
+ ImageAspectFlags::COLOR,
)],
);
-
- self.width = width;
- self.height = height;
}
- if self.glyph_atlas_image.is_null() {
- let image = gpu.create_image(&ImageDesc {
+ if width != self.width || height != self.height {
+ gpu.destroy_image(frame, self.depth_image);
+ gpu.destroy_image(frame, self.render_target_image);
+
+ self.depth_image = gpu.create_image(&ImageDesc {
memory_location: MemoryLocation::Device,
- usage: ImageUsageFlags::SAMPLED | ImageUsageFlags::TRANSFER,
host_mapped: false,
+ usage: ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT,
dimension: ImageDimension::Type2d,
- format: ImageFormat::R8_SRGB,
+ format: ImageFormat::DEPTH_F32,
tiling: ImageTiling::Optimal,
- width: ui_state.glyph_cache.width() as u32,
- height: ui_state.glyph_cache.height() as u32,
+ width,
+ height,
+ depth: 1,
+ layer_count: 1,
+ mip_levels: 1,
+ });
+
+ self.render_target_image = gpu.create_image(&ImageDesc {
+ memory_location: MemoryLocation::Device,
+ host_mapped: false,
+ usage: ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::STORAGE,
+ dimension: ImageDimension::Type2d,
+ format: ImageFormat::RGBA16_FLOAT,
+ tiling: ImageTiling::Optimal,
+ width,
+ height,
depth: 1,
layer_count: 1,
mip_levels: 1,
None,
&[ImageBarrier::layout_optimal(
&[Access::None],
- &[Access::ShaderSampledImageRead],
- image,
- ImageAspectFlags::COLOR,
+ &[Access::DepthStencilAttachmentWrite],
+ self.depth_image,
+ ImageAspectFlags::DEPTH,
)],
);
- self.glyph_atlas_image = image;
+ self.width = width;
+ self.height = height;
}
- let atlas_width = ui_state.glyph_cache.width() as u32;
- let atlas_height = ui_state.glyph_cache.height() as u32;
-
let (touched_glyphs, glyph_texture) = ui_state.glyph_cache.update_atlas();
// If the atlas has been updated, we need to upload it to the GPU.
if let Some(texture) = glyph_texture {
- let image = self.glyph_atlas_image;
-
let buffer = gpu.request_transient_buffer_with_data(
frame,
thread_token,
&[ImageBarrier::layout_optimal(
&[Access::ShaderSampledImageRead],
&[Access::TransferWrite],
- image,
+ self.glyph_atlas_image,
ImageAspectFlags::COLOR,
)],
);
gpu.cmd_copy_buffer_to_image(
cmd_encoder,
buffer.to_arg(),
- image,
+ self.glyph_atlas_image,
ImageLayout::Optimal,
&[BufferImageCopy {
image_extent: Extent3d {
&[ImageBarrier::layout_optimal(
&[Access::TransferWrite],
&[Access::FragmentShaderSampledImageRead],
- image,
+ self.glyph_atlas_image,
ImageAspectFlags::COLOR,
)],
);
}
+ gpu.cmd_barrier(
+ cmd_encoder,
+ None,
+ &[ImageBarrier::layout_optimal(
+ &[Access::None],
+ &[Access::ColorAttachmentWrite],
+ self.render_target_image,
+ ImageAspectFlags::COLOR,
+ )],
+ );
+
gpu.cmd_begin_rendering(
cmd_encoder,
&RenderingDesc {
width,
height,
color_attachments: &[RenderingAttachment {
- image: swapchain_image,
+ image: self.render_target_image,
load_op: LoadOp::Clear(ClearValue::ColorF32([1.0, 1.0, 1.0, 1.0])),
store_op: StoreOp::Store,
}],
Bind {
binding: 3,
array_element: 0,
- typed: TypedBind::Image(&[(ImageLayout::Optimal, image)]),
+ typed: TypedBind::SampledImage(&[(ImageLayout::Optimal, image)]),
},
],
);
Bind {
binding: 5,
array_element: 0,
- typed: TypedBind::Image(&[(
+ typed: TypedBind::SampledImage(&[(
ImageLayout::Optimal,
self.glyph_atlas_image,
)]),
}
gpu.cmd_end_rendering(cmd_encoder);
+
+ gpu.cmd_barrier(
+ cmd_encoder,
+ None,
+ &[ImageBarrier {
+ prev_access: &[Access::ColorAttachmentWrite],
+ prev_layout: ImageLayout::Optimal,
+ next_access: &[Access::ShaderOtherRead],
+ next_layout: ImageLayout::General,
+ image: self.render_target_image,
+ subresource_range: ImageSubresourceRange::default(),
+ }],
+ );
+
+ gpu.cmd_compute_touch_swapchain(cmd_encoder, swapchain_image);
+
+ gpu.cmd_set_pipeline(cmd_encoder, self.display_transform_pipeline.pipeline);
+
+ gpu.cmd_set_bind_group(
+ frame,
+ cmd_encoder,
+ self.display_transform_pipeline.bind_group_layout,
+ 0,
+ &[
+ Bind {
+ binding: 0,
+ array_element: 0,
+ typed: TypedBind::Sampler(&[self.display_transform_pipeline.sampler]),
+ },
+ Bind {
+ binding: 1,
+ array_element: 0,
+ typed: TypedBind::SampledImage(&[(
+ ImageLayout::Optimal,
+ self.images.0[ImageRes::TonyMcMapfaceLut as usize],
+ )]),
+ },
+ Bind {
+ binding: 2,
+ array_element: 0,
+ typed: TypedBind::StorageImage(&[(
+ ImageLayout::General,
+ self.render_target_image,
+ )]),
+ },
+ Bind {
+ binding: 3,
+ array_element: 0,
+ typed: TypedBind::StorageImage(&[(ImageLayout::General, swapchain_image)]),
+ },
+ ],
+ );
+
+ gpu.cmd_dispatch(cmd_encoder, (self.width + 7) / 8, (self.height + 7) / 8, 1);
}
gpu.submit(frame, cmd_encoder);
}
{
let frame = &frame;
- let formats = &[ImageFormat::RGBA8_SRGB];
+ let formats = &[ImageFormat::A2R10G10B10_UNORM];
let mut swapchain_images = [Image::default(); 1];
let SwapchainImage { width, height } = loop {
window.upcast(),
drawable_width,
drawable_height,
- ImageUsageFlags::COLOR_ATTACHMENT,
+ ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::STORAGE,
formats,
&mut swapchain_images,
) {
}
};
- let [swapchain_image_srgb] = swapchain_images;
+ let [swapchain_image_unorm] = swapchain_images;
let tick_start = Instant::now();
'tick: loop {
&game_state,
width,
height,
- swapchain_image_srgb,
+ swapchain_image_unorm,
);
}