mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 16:51:53 +03:00
Merge pull request #351 from rtfeldman/triangle
Render a triangle in the editor
This commit is contained in:
commit
be5641e35a
26
Cargo.lock
generated
26
Cargo.lock
generated
@ -103,6 +103,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
@ -720,6 +730,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"smallvec",
|
||||
"winapi 0.3.8",
|
||||
"x11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1812,6 +1823,7 @@ dependencies = [
|
||||
name = "roc_editor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"gfx-backend-dx12",
|
||||
"gfx-backend-metal",
|
||||
@ -1844,6 +1856,7 @@ dependencies = [
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"roc_uniq",
|
||||
"serde",
|
||||
"target-lexicon",
|
||||
"tokio",
|
||||
"winit",
|
||||
@ -2183,6 +2196,9 @@ name = "serde"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
@ -2752,6 +2768,16 @@ dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.18.5"
|
||||
|
@ -51,6 +51,9 @@ target-lexicon = "0.10"
|
||||
winit = "0.22"
|
||||
image = "0.23"
|
||||
gfx-hal = "0.5"
|
||||
glsl-to-spirv = "0.1"
|
||||
bincode = "1.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies.backend]
|
||||
package = "gfx-backend-metal"
|
||||
@ -62,6 +65,7 @@ version = "0.5"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.backend]
|
||||
package = "gfx-backend-vulkan"
|
||||
features = ["x11"]
|
||||
version = "0.5"
|
||||
|
||||
[build-dependencies]
|
||||
@ -73,4 +77,3 @@ maplit = "1.0.1"
|
||||
indoc = "0.3.3"
|
||||
quickcheck = "0.8"
|
||||
quickcheck_macros = "0.8"
|
||||
|
||||
|
8
editor/shaders/triangle.frag
Normal file
8
editor/shaders/triangle.frag
Normal file
@ -0,0 +1,8 @@
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
layout(location = 0) out vec4 fragment_color;
|
||||
|
||||
void main() {
|
||||
fragment_color = vec4(0.5, 0.5, 1.0, 1.0);
|
||||
}
|
15
editor/shaders/triangle.vert
Normal file
15
editor/shaders/triangle.vert
Normal file
@ -0,0 +1,15 @@
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
|
||||
void main() {
|
||||
vec2 position;
|
||||
if (gl_VertexIndex == 0) {
|
||||
position = vec2(0.0, -0.5);
|
||||
} else if (gl_VertexIndex == 1) {
|
||||
position = vec2(-0.5, 0.5);
|
||||
} else if (gl_VertexIndex == 2) {
|
||||
position = vec2(0.5, 0.5);
|
||||
}
|
||||
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
@ -1,4 +1,11 @@
|
||||
use gfx_hal::{
|
||||
device::Device,
|
||||
window::{Extent2D, PresentationSurface, Surface},
|
||||
Instance,
|
||||
};
|
||||
use glsl_to_spirv::ShaderType;
|
||||
use std::io;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::path::Path;
|
||||
|
||||
/// The editor is actually launched from the CLI if you pass it zero arguments,
|
||||
@ -11,17 +18,288 @@ 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
|
||||
const WINDOW_SIZE: [u32; 2] = [512, 512];
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let (logical_window_size, physical_window_size) = {
|
||||
use winit::dpi::{LogicalSize, PhysicalSize};
|
||||
|
||||
let dpi = event_loop.primary_monitor().scale_factor();
|
||||
let logical: LogicalSize<u32> = WINDOW_SIZE.into();
|
||||
let physical: PhysicalSize<u32> = logical.to_physical(dpi);
|
||||
|
||||
(logical, physical)
|
||||
};
|
||||
|
||||
let mut surface_extent = Extent2D {
|
||||
width: physical_window_size.width,
|
||||
height: physical_window_size.height,
|
||||
};
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("roc")
|
||||
.with_inner_size(logical_window_size)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
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;
|
||||
|
||||
// TODO try ControlFlow::Poll and see if it affects input latency.
|
||||
// Otherwise, this seems like a better default for minimizing idle
|
||||
// CPU usage and battry drain. (Might want to switch to Poll whenever
|
||||
@ -30,17 +308,183 @@ fn run_event_loop() {
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
event: window_event,
|
||||
..
|
||||
} => {
|
||||
println!("✈️ Thank you for flying Roc Airlines!");
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
} => match window_event {
|
||||
WindowEvent::CloseRequested => {
|
||||
println!("✈️ Thank you for flying Roc Airlines!");
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
WindowEvent::Resized(dims) => {
|
||||
surface_extent = Extent2D {
|
||||
width: dims.width,
|
||||
height: dims.height,
|
||||
};
|
||||
should_configure_swapchain = true;
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
surface_extent = Extent2D {
|
||||
width: new_inner_size.width,
|
||||
height: new_inner_size.height,
|
||||
};
|
||||
should_configure_swapchain = true;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
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…
Reference in New Issue
Block a user