mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Render a triangle
Largely following https://github.com/mistodon/gfx-hal-tutorials - code is CC0 licensed. Lovely tutorials!
This commit is contained in:
parent
05e13e9842
commit
0c9b895912
3 changed files with 410 additions and 6 deletions
|
@ -18,11 +18,54 @@ pub fn launch(_filepaths: &[&Path]) -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
||||
|
||||
struct Resources<B: gfx_hal::Backend> {
|
||||
instance: B::Instance,
|
||||
surface: B::Surface,
|
||||
device: B::Device,
|
||||
render_passes: Vec<B::RenderPass>,
|
||||
pipeline_layouts: Vec<B::PipelineLayout>,
|
||||
pipelines: Vec<B::GraphicsPipeline>,
|
||||
command_pool: B::CommandPool,
|
||||
submission_complete_fence: B::Fence,
|
||||
rendering_complete_semaphore: B::Semaphore,
|
||||
}
|
||||
|
||||
struct ResourceHolder<B: gfx_hal::Backend>(ManuallyDrop<Resources<B>>);
|
||||
|
||||
impl<B: gfx_hal::Backend> Drop for ResourceHolder<B> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let Resources {
|
||||
instance,
|
||||
mut surface,
|
||||
device,
|
||||
command_pool,
|
||||
render_passes,
|
||||
pipeline_layouts,
|
||||
pipelines,
|
||||
submission_complete_fence,
|
||||
rendering_complete_semaphore,
|
||||
} = ManuallyDrop::take(&mut self.0);
|
||||
|
||||
device.destroy_semaphore(rendering_complete_semaphore);
|
||||
device.destroy_fence(submission_complete_fence);
|
||||
for pipeline in pipelines {
|
||||
device.destroy_graphics_pipeline(pipeline);
|
||||
}
|
||||
for pipeline_layout in pipeline_layouts {
|
||||
device.destroy_pipeline_layout(pipeline_layout);
|
||||
}
|
||||
for render_pass in render_passes {
|
||||
device.destroy_render_pass(render_pass);
|
||||
}
|
||||
device.destroy_command_pool(command_pool);
|
||||
surface.unconfigure_swapchain(&device);
|
||||
instance.destroy_surface(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_event_loop() {
|
||||
// TODO do a better window size
|
||||
|
@ -53,6 +96,206 @@ fn run_event_loop() {
|
|||
|
||||
let mut should_configure_swapchain = true;
|
||||
|
||||
let (instance, surface, adapter) = {
|
||||
let instance = backend::Instance::create("roc_editor", 1).expect("Backend not supported");
|
||||
|
||||
let surface = unsafe {
|
||||
instance
|
||||
.create_surface(&window)
|
||||
.expect("Failed to create surface for window")
|
||||
};
|
||||
|
||||
let adapter = instance.enumerate_adapters().remove(0);
|
||||
|
||||
(instance, surface, adapter)
|
||||
};
|
||||
|
||||
let (device, mut queue_group) = {
|
||||
use gfx_hal::queue::QueueFamily;
|
||||
|
||||
let queue_family = adapter
|
||||
.queue_families
|
||||
.iter()
|
||||
.find(|family| {
|
||||
surface.supports_queue_family(family) && family.queue_type().supports_graphics()
|
||||
})
|
||||
.expect("No compatible queue family found");
|
||||
|
||||
let mut gpu = unsafe {
|
||||
use gfx_hal::adapter::PhysicalDevice;
|
||||
|
||||
adapter
|
||||
.physical_device
|
||||
.open(&[(queue_family, &[1.0])], gfx_hal::Features::empty())
|
||||
.expect("Failed to open device")
|
||||
};
|
||||
|
||||
(gpu.device, gpu.queue_groups.pop().unwrap())
|
||||
};
|
||||
|
||||
let (command_pool, mut command_buffer) = unsafe {
|
||||
use gfx_hal::command::Level;
|
||||
use gfx_hal::pool::{CommandPool, CommandPoolCreateFlags};
|
||||
|
||||
let mut command_pool = device
|
||||
.create_command_pool(queue_group.family, CommandPoolCreateFlags::empty())
|
||||
.expect("Out of memory");
|
||||
|
||||
let command_buffer = command_pool.allocate_one(Level::Primary);
|
||||
|
||||
(command_pool, command_buffer)
|
||||
};
|
||||
|
||||
let surface_color_format = {
|
||||
use gfx_hal::format::{ChannelType, Format};
|
||||
|
||||
let supported_formats = surface
|
||||
.supported_formats(&adapter.physical_device)
|
||||
.unwrap_or_else(|| vec![]);
|
||||
|
||||
let default_format = *supported_formats.get(0).unwrap_or(&Format::Rgba8Srgb);
|
||||
|
||||
supported_formats
|
||||
.into_iter()
|
||||
.find(|format| format.base_format().1 == ChannelType::Srgb)
|
||||
.unwrap_or(default_format)
|
||||
};
|
||||
|
||||
let render_pass = {
|
||||
use gfx_hal::image::Layout;
|
||||
use gfx_hal::pass::{
|
||||
Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp, SubpassDesc,
|
||||
};
|
||||
|
||||
let color_attachment = Attachment {
|
||||
format: Some(surface_color_format),
|
||||
samples: 1,
|
||||
ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store),
|
||||
stencil_ops: AttachmentOps::DONT_CARE,
|
||||
layouts: Layout::Undefined..Layout::Present,
|
||||
};
|
||||
|
||||
let subpass = SubpassDesc {
|
||||
colors: &[(0, Layout::ColorAttachmentOptimal)],
|
||||
depth_stencil: None,
|
||||
inputs: &[],
|
||||
resolves: &[],
|
||||
preserves: &[],
|
||||
};
|
||||
|
||||
unsafe {
|
||||
device
|
||||
.create_render_pass(&[color_attachment], &[subpass], &[])
|
||||
.expect("Out of memory")
|
||||
}
|
||||
};
|
||||
|
||||
let pipeline_layout = unsafe {
|
||||
device
|
||||
.create_pipeline_layout(&[], &[])
|
||||
.expect("Out of memory")
|
||||
};
|
||||
|
||||
let vertex_shader = include_str!("../shaders/triangle.vert");
|
||||
let fragment_shader = include_str!("../shaders/triangle.frag");
|
||||
|
||||
/// Create a pipeline with the given layout and shaders.
|
||||
unsafe fn make_pipeline<B: gfx_hal::Backend>(
|
||||
device: &B::Device,
|
||||
render_pass: &B::RenderPass,
|
||||
pipeline_layout: &B::PipelineLayout,
|
||||
vertex_shader: &str,
|
||||
fragment_shader: &str,
|
||||
) -> B::GraphicsPipeline {
|
||||
use gfx_hal::pass::Subpass;
|
||||
use gfx_hal::pso::{
|
||||
BlendState, ColorBlendDesc, ColorMask, EntryPoint, Face, GraphicsPipelineDesc,
|
||||
GraphicsShaderSet, Primitive, Rasterizer, Specialization,
|
||||
};
|
||||
|
||||
let vertex_shader_module = device
|
||||
.create_shader_module(&compile_shader(vertex_shader, ShaderType::Vertex))
|
||||
.expect("Failed to create vertex shader module");
|
||||
|
||||
let fragment_shader_module = device
|
||||
.create_shader_module(&compile_shader(fragment_shader, ShaderType::Fragment))
|
||||
.expect("Failed to create fragment shader module");
|
||||
|
||||
let (vs_entry, fs_entry) = (
|
||||
EntryPoint {
|
||||
entry: "main",
|
||||
module: &vertex_shader_module,
|
||||
specialization: Specialization::default(),
|
||||
},
|
||||
EntryPoint {
|
||||
entry: "main",
|
||||
module: &fragment_shader_module,
|
||||
specialization: Specialization::default(),
|
||||
},
|
||||
);
|
||||
|
||||
let shader_entries = GraphicsShaderSet {
|
||||
vertex: vs_entry,
|
||||
hull: None,
|
||||
domain: None,
|
||||
geometry: None,
|
||||
fragment: Some(fs_entry),
|
||||
};
|
||||
|
||||
let mut pipeline_desc = GraphicsPipelineDesc::new(
|
||||
shader_entries,
|
||||
Primitive::TriangleList,
|
||||
Rasterizer {
|
||||
cull_face: Face::BACK,
|
||||
..Rasterizer::FILL
|
||||
},
|
||||
pipeline_layout,
|
||||
Subpass {
|
||||
index: 0,
|
||||
main_pass: render_pass,
|
||||
},
|
||||
);
|
||||
|
||||
pipeline_desc.blender.targets.push(ColorBlendDesc {
|
||||
mask: ColorMask::ALL,
|
||||
blend: Some(BlendState::ALPHA),
|
||||
});
|
||||
|
||||
let pipeline = device
|
||||
.create_graphics_pipeline(&pipeline_desc, None)
|
||||
.expect("Failed to create graphics pipeline");
|
||||
|
||||
device.destroy_shader_module(vertex_shader_module);
|
||||
device.destroy_shader_module(fragment_shader_module);
|
||||
|
||||
pipeline
|
||||
};
|
||||
|
||||
let pipeline = unsafe {
|
||||
make_pipeline::<backend::Backend>(
|
||||
&device,
|
||||
&render_pass,
|
||||
&pipeline_layout,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
)
|
||||
};
|
||||
|
||||
let submission_complete_fence = device.create_fence(true).expect("Out of memory");
|
||||
let rendering_complete_semaphore = device.create_semaphore().expect("Out of memory");
|
||||
let mut resource_holder: ResourceHolder<backend::Backend> =
|
||||
ResourceHolder(ManuallyDrop::new(Resources {
|
||||
instance,
|
||||
surface,
|
||||
device,
|
||||
command_pool,
|
||||
render_passes: vec![render_pass],
|
||||
pipeline_layouts: vec![pipeline_layout],
|
||||
pipelines: vec![pipeline],
|
||||
submission_complete_fence,
|
||||
rendering_complete_semaphore,
|
||||
}));
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event_loop::ControlFlow;
|
||||
|
@ -90,9 +333,158 @@ fn run_event_loop() {
|
|||
},
|
||||
Event::MainEventsCleared => window.request_redraw(),
|
||||
Event::RedrawRequested(_) => {
|
||||
// TODO render the editor
|
||||
let res: &mut Resources<_> = &mut resource_holder.0;
|
||||
let render_pass = &res.render_passes[0];
|
||||
let pipeline = &res.pipelines[0];
|
||||
|
||||
unsafe {
|
||||
use gfx_hal::pool::CommandPool;
|
||||
|
||||
// We refuse to wait more than a second, to avoid hanging.
|
||||
let render_timeout_ns = 1_000_000_000;
|
||||
|
||||
res.device
|
||||
.wait_for_fence(&res.submission_complete_fence, render_timeout_ns)
|
||||
.expect("Out of memory or device lost");
|
||||
|
||||
res.device
|
||||
.reset_fence(&res.submission_complete_fence)
|
||||
.expect("Out of memory");
|
||||
|
||||
res.command_pool.reset(false);
|
||||
}
|
||||
|
||||
if should_configure_swapchain {
|
||||
use gfx_hal::window::SwapchainConfig;
|
||||
|
||||
let caps = res.surface.capabilities(&adapter.physical_device);
|
||||
|
||||
let mut swapchain_config =
|
||||
SwapchainConfig::from_caps(&caps, surface_color_format, surface_extent);
|
||||
|
||||
// This seems to fix some fullscreen slowdown on macOS.
|
||||
if caps.image_count.contains(&3) {
|
||||
swapchain_config.image_count = 3;
|
||||
}
|
||||
|
||||
surface_extent = swapchain_config.extent;
|
||||
|
||||
unsafe {
|
||||
res.surface
|
||||
.configure_swapchain(&res.device, swapchain_config)
|
||||
.expect("Failed to configure swapchain");
|
||||
};
|
||||
|
||||
should_configure_swapchain = false;
|
||||
}
|
||||
|
||||
let surface_image = unsafe {
|
||||
// We refuse to wait more than a second, to avoid hanging.
|
||||
let acquire_timeout_ns = 1_000_000_000;
|
||||
|
||||
match res.surface.acquire_image(acquire_timeout_ns) {
|
||||
Ok((image, _)) => image,
|
||||
Err(_) => {
|
||||
should_configure_swapchain = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let framebuffer = unsafe {
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use gfx_hal::image::Extent;
|
||||
|
||||
res.device
|
||||
.create_framebuffer(
|
||||
render_pass,
|
||||
vec![surface_image.borrow()],
|
||||
Extent {
|
||||
width: surface_extent.width,
|
||||
height: surface_extent.height,
|
||||
depth: 1,
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let viewport = {
|
||||
use gfx_hal::pso::{Rect, Viewport};
|
||||
|
||||
Viewport {
|
||||
rect: Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: surface_extent.width as i16,
|
||||
h: surface_extent.height as i16,
|
||||
},
|
||||
depth: 0.0..1.0,
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
use gfx_hal::command::{
|
||||
ClearColor, ClearValue, CommandBuffer, CommandBufferFlags, SubpassContents,
|
||||
};
|
||||
|
||||
command_buffer.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
|
||||
|
||||
command_buffer.set_viewports(0, &[viewport.clone()]);
|
||||
command_buffer.set_scissors(0, &[viewport.rect]);
|
||||
command_buffer.begin_render_pass(
|
||||
render_pass,
|
||||
&framebuffer,
|
||||
viewport.rect,
|
||||
&[ClearValue {
|
||||
color: ClearColor {
|
||||
float32: [0.0, 0.0, 0.0, 1.0],
|
||||
},
|
||||
}],
|
||||
SubpassContents::Inline,
|
||||
);
|
||||
command_buffer.bind_graphics_pipeline(pipeline);
|
||||
command_buffer.draw(0..3, 0..1);
|
||||
command_buffer.end_render_pass();
|
||||
command_buffer.finish();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
use gfx_hal::queue::{CommandQueue, Submission};
|
||||
|
||||
let submission = Submission {
|
||||
command_buffers: vec![&command_buffer],
|
||||
wait_semaphores: None,
|
||||
signal_semaphores: vec![&res.rendering_complete_semaphore],
|
||||
};
|
||||
|
||||
queue_group.queues[0].submit(submission, Some(&res.submission_complete_fence));
|
||||
let result = queue_group.queues[0].present_surface(
|
||||
&mut res.surface,
|
||||
surface_image,
|
||||
Some(&res.rendering_complete_semaphore),
|
||||
);
|
||||
|
||||
should_configure_swapchain |= result.is_err();
|
||||
|
||||
res.device.destroy_framebuffer(framebuffer);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Compile some GLSL shader source to SPIR-V.
|
||||
/// TODO do this at build time - possibly in CI only
|
||||
fn compile_shader(glsl: &str, shader_type: ShaderType) -> Vec<u32> {
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
let mut compiled_file =
|
||||
glsl_to_spirv::compile(glsl, shader_type).expect("Failed to compile shader");
|
||||
|
||||
let mut spirv_bytes = vec![];
|
||||
compiled_file.read_to_end(&mut spirv_bytes).unwrap();
|
||||
|
||||
gfx_hal::pso::read_spirv(Cursor::new(&spirv_bytes)).expect("Invalid SPIR-V")
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue