aboutsummaryrefslogtreecommitdiff
path: root/stockton-render
diff options
context:
space:
mode:
Diffstat (limited to 'stockton-render')
-rw-r--r--stockton-render/Cargo.toml1
-rw-r--r--stockton-render/src/draw/context.rs432
-rw-r--r--stockton-render/src/draw/frame.rs51
-rw-r--r--stockton-render/src/draw/mod.rs21
-rw-r--r--stockton-render/src/error.rs99
-rw-r--r--stockton-render/src/lib.rs37
6 files changed, 627 insertions, 14 deletions
diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml
index 40c7bb4..4dd3e1f 100644
--- a/stockton-render/Cargo.toml
+++ b/stockton-render/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2018"
stockton-types = { path = "../stockton-types" }
winit = "0.19.1"
gfx-hal = "0.2.0"
+arrayvec = "0.4.10"
[features]
default = ["vulkan"]
diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs
new file mode 100644
index 0000000..349b168
--- /dev/null
+++ b/stockton-render/src/draw/context.rs
@@ -0,0 +1,432 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Deals with all the Vulkan/HAL details.
+use core::mem::ManuallyDrop;
+
+use crate::error::{CreationError, FrameError};
+use super::frame::FrameCell;
+
+use arrayvec::ArrayVec;
+
+use winit::Window;
+
+// Trait imports
+use hal::{Surface as SurfaceTrait, Instance as InstanceTrait, QueueFamily as QFTrait, PhysicalDevice as PDTrait, Device as DeviceTrait, Swapchain as SwapchainTrait};
+
+use hal::{Graphics, Gpu, Features, SwapchainConfig, Submission};
+use hal::pass::{SubpassDesc, AttachmentOps, Attachment, AttachmentStoreOp, AttachmentLoadOp};
+use hal::image::{Usage, Layout, SubresourceRange, ViewKind, Extent};
+use hal::format::{ChannelType, Format, Swizzle, Aspects};
+use hal::pool::{CommandPoolCreateFlags, CommandPool};
+use hal::command::{ClearValue, ClearColor};
+use hal::pso::{Rect, PipelineStage};
+use hal::queue::family::QueueGroup;
+use hal::window::{PresentMode};
+use hal::adapter::Adapter;
+
+use back::{Instance};
+use back::{Backend};
+
+/// Contains all the hal related stuff.
+/// In the end, this takes some 3D points and puts it on the screen.
+pub struct RenderingContext<'a> {
+ /// The window to render to
+ window: &'a Window,
+
+ /// The instance of vulkan (or another backend) being used.
+ /// For vulkan, the name is always "stockton" & version is always 1
+ instance: ManuallyDrop<Instance>,
+
+ /// The surface being drawn to.
+ /// Mostly an abstraction over Window.
+ surface: <Backend as hal::Backend>::Surface,
+
+ /// Points to something that's used for rendering & presentation.
+ adapter: Adapter<Backend>,
+
+ /// Lets us actually render & present
+ device: ManuallyDrop<<Backend as hal::Backend>::Device>,
+
+ /// A collection of images that we create then present.
+ swapchain: ManuallyDrop<<Backend as hal::Backend>::Swapchain>,
+
+ /// The size of where we're rendering to.
+ render_area: Rect,
+
+ /// Describes the render pipeline.
+ render_pass: ManuallyDrop<<Backend as hal::Backend>::RenderPass>,
+
+ /// The format of the images.
+ format: Format,
+
+ /// What part(s) of the device will do our work
+ queue_group: ManuallyDrop<QueueGroup<Backend, Graphics>>,
+
+ /// A parent for all our command buffers.
+ command_pool: ManuallyDrop<CommandPool<Backend, Graphics>>,
+
+ /// Each FrameCell draws a frame to its own image & command buffer.
+ cells: Vec<FrameCell>,
+
+ /// Images from the swapchain to render to.
+ backbuffer: Vec<<Backend as hal::Backend>::Image>,
+
+ /// The images we're rendering to with some additional metadata.
+ image_views: Vec<<Backend as hal::Backend>::ImageView>,
+
+ /// What the command buffers will actually target.
+ framebuffers: Vec<<Backend as hal::Backend>::Framebuffer>,
+
+ /// The maximum number of frames we'll have ready for presentation at any time.
+ frames_in_flight: usize,
+
+ /// Track which framecell is up next.
+ current_frame: usize
+}
+
+impl<'a> RenderingContext<'a> {
+ /// Create a new RenderingContext for the given window.
+ pub fn new(window: &'a Window) -> Result<Self, CreationError> {
+ let instance = Instance::create("stockton", 1);
+ let mut surface = instance.create_surface(&window);
+
+ // find a suitable adapter
+ // one that can render graphics & present to our surface
+ let adapter = instance
+ .enumerate_adapters()
+ .into_iter()
+ .find(|a| {
+ a.queue_families
+ .iter()
+ .any(|qf| qf.supports_graphics() && surface.supports_queue_family(qf))
+ })
+ .ok_or(CreationError::NoAdapter)?;
+
+ // from that adapter, get the device & queue group
+ let (device, queue_group) = {
+ let queue_family = adapter
+ .queue_families
+ .iter()
+ .find(|qf| qf.supports_graphics() && surface.supports_queue_family(qf))
+ .ok_or(CreationError::NoQueueFamily)?;
+
+ let Gpu { device, mut queues } = unsafe {
+ adapter
+ .physical_device
+ .open(&[(&queue_family, &[1.0; 1])], Features::empty())
+ .map_err(|_| CreationError::NoPhysicalDevice)?
+ };
+
+ let queue_group = queues
+ .take::<Graphics>(queue_family.id())
+ .ok_or(CreationError::NoQueueGroup)?;
+
+ if queue_group.queues.is_empty() {
+ return Err(CreationError::NoCommandQueues)
+ };
+
+ (device, queue_group)
+ };
+
+ // Create the swapchain
+ let (swapchain, extent, backbuffer, format, frames_in_flight) = {
+ let (caps, formats, modes) = surface.compatibility(&adapter.physical_device);
+
+ let present_mode = {
+ use hal::window::PresentMode::*;
+ [Mailbox, Fifo, Relaxed, Immediate]
+ .iter()
+ .cloned()
+ .find(|pm| modes.contains(pm))
+ .ok_or(CreationError::NoPresentModes)?
+ };
+
+ let composite_alpha = {
+ use hal::window::CompositeAlpha;
+ [CompositeAlpha::OPAQUE, CompositeAlpha::INHERIT, CompositeAlpha::PREMULTIPLIED, CompositeAlpha::POSTMULTIPLIED]
+ .iter()
+ .cloned()
+ .find(|ca| caps.composite_alpha.contains(*ca))
+ .ok_or(CreationError::NoCompositeAlphas)?
+ };
+
+ let format = match formats {
+ None => Format::Rgba8Srgb,
+ Some(formats) => match formats
+ .iter()
+ .find(|format| format.base_format().1 == ChannelType::Srgb)
+ .cloned() {
+ Some(srgb_format) => srgb_format,
+ None => formats.get(0).cloned().ok_or(CreationError::NoImageFormats)?,
+ },
+ };
+
+ let extent = caps.extents.end;
+ let image_count = if present_mode == PresentMode::Mailbox {
+ (caps.image_count.end - 1).min(3)
+ } else {
+ (caps.image_count.end - 1).min(2)
+ };
+
+ let image_layers = 1;
+ let image_usage = if caps.usage.contains(Usage::COLOR_ATTACHMENT) {
+ Usage::COLOR_ATTACHMENT
+ } else {
+ Err(CreationError::NoColor)?
+ };
+
+
+ let swapchain_config = SwapchainConfig {
+ present_mode,
+ composite_alpha,
+ format,
+ extent,
+ image_count,
+ image_layers,
+ image_usage,
+ };
+ let (swapchain, backbuffer) = unsafe {
+ device
+ .create_swapchain(&mut surface, swapchain_config, None)
+ .map_err(|e| CreationError::SwapchainFailed { 0: e})?
+ };
+
+ (swapchain, extent, backbuffer, format, image_count as usize)
+ };
+
+ let render_area = Rect {
+ x: 0, y: 0,
+ w: extent.width as i16,
+ h: extent.height as i16
+ };
+
+ // Create render pass
+ let render_pass = {
+ let color_attachment = Attachment {
+ format: Some(format),
+ samples: 1,
+ ops: AttachmentOps {
+ load: AttachmentLoadOp::Clear,
+ store: 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], &[])
+ .map_err(|e| CreationError::RenderPassFailed { 0: e })?
+ }
+ };
+
+ // Make the command pool
+ let mut command_pool = unsafe {
+ device
+ .create_command_pool_typed(&queue_group, CommandPoolCreateFlags::RESET_INDIVIDUAL)
+ .map_err(|e| CreationError::CommandPoolFailed { 0: e })?
+ };
+
+ // Create framecells
+ let cells = (0..frames_in_flight)
+ .map(|_| {
+ let image_available = device.create_semaphore().map_err(|e| CreationError::SemaphoreFailed { 0: e })?;
+ let render_finished = device.create_semaphore().map_err(|e| CreationError::SemaphoreFailed { 0: e })?;
+ let frame_presented = device.create_fence(true).map_err(|e| CreationError::FenceFailed { 0: e })?;
+
+
+ let command_buffer = command_pool.acquire_command_buffer();
+
+ Ok(FrameCell {
+ command_buffer,
+ image_available,
+ render_finished,
+ frame_presented
+ })
+ })
+ .collect::<Result<Vec<FrameCell>, CreationError>>()?;
+
+ // Create image views and framebuffers
+ let image_views = backbuffer.iter().map(
+ |image| unsafe {
+ device.create_image_view(
+ &image,
+ ViewKind::D2,
+ format,
+ Swizzle::NO,
+ SubresourceRange {
+ aspects: Aspects::COLOR,
+ levels: 0..1,
+ layers: 0..1,
+ },
+ ).map_err(|e| CreationError::ImageViewFailed { 0: e })
+ }
+ ).collect::<Result<Vec<<Backend as hal::Backend>::ImageView>, CreationError>>()?;
+
+ let framebuffers = image_views.iter().map(
+ |image_view| unsafe {
+ device.create_framebuffer(
+ &render_pass,
+ vec![image_view],
+ Extent {
+ width: extent.width as u32,
+ height: extent.height as u32,
+ depth: 1
+ }
+ ).map_err(|e| CreationError::FramebufferFailed { 0: e })
+ }
+ ).collect::<Result<Vec<<Backend as hal::Backend>::Framebuffer>, CreationError>>()?;
+
+
+ Ok(RenderingContext {
+ instance: ManuallyDrop::new(instance),
+
+ window, surface,
+
+ device: ManuallyDrop::new(device),
+ adapter,
+ queue_group: ManuallyDrop::new(queue_group),
+
+ swapchain: ManuallyDrop::new(swapchain),
+ render_area, format, frames_in_flight,
+
+ framebuffers, image_views, backbuffer,
+
+ render_pass: ManuallyDrop::new(render_pass),
+ cells,
+
+ command_pool: ManuallyDrop::new(command_pool),
+
+ current_frame: 0
+ })
+ }
+
+ /// Draw a frame of color
+ pub fn draw_clear(&mut self, color: [f32; 4]) -> Result<(), FrameError> {
+ // Advance the frame before early outs to prevent fuckery.
+ self.current_frame = (self.current_frame + 1) % self.frames_in_flight;
+
+ let cell = &mut self.cells[self.current_frame];
+
+ // Get the image of the swapchain to present to.
+ let (i, _) = unsafe {
+ self.swapchain
+ .acquire_image(core::u64::MAX, Some(&cell.image_available), None)
+ .map_err(|e| FrameError::AcquisitionError { 0: e })?
+ };
+ let i = i as usize;
+
+ // Make sure frame has been presented since whenever it was last drawn.
+ unsafe {
+ self.device
+ .wait_for_fence(&cell.frame_presented, core::u64::MAX)
+ .map_err(|e| FrameError::FenceWaitError { 0: e })?;
+ self.device
+ .reset_fence(&cell.frame_presented)
+ .map_err(|e| FrameError::FenceResetError { 0: e })?;
+ }
+
+ // Record commands.
+ unsafe {
+ let clear_values = [ClearValue::Color(ClearColor::Float(color))];
+ cell.command_buffer.begin();
+ cell.command_buffer.begin_render_pass_inline(
+ &self.render_pass,
+ &self.framebuffers[i],
+ self.render_area,
+ clear_values.iter(),
+ );
+ cell.command_buffer.finish();
+ }
+
+
+ // Prepare submission
+ let command_buffers: ArrayVec<[_; 1]> = [&cell.command_buffer].into();
+
+ let wait_semaphores: ArrayVec<[_; 1]> = [(&cell.image_available, PipelineStage::COLOR_ATTACHMENT_OUTPUT)].into();
+ let signal_semaphores: ArrayVec<[_; 1]> = [&cell.render_finished].into();
+ let present_wait_semaphores: ArrayVec<[_; 1]> = [&cell.render_finished].into();
+ let submission = Submission {
+ command_buffers,
+ wait_semaphores,
+ signal_semaphores,
+ };
+
+ // Submit it for rendering and presentation.
+ let command_queue = &mut self.queue_group.queues[0];
+
+ unsafe {
+ command_queue.submit(submission, Some(&cell.frame_presented));
+ self.swapchain
+ .present(command_queue, i as u32, present_wait_semaphores)
+ .map_err(|e| FrameError::PresentError { 0: e })?
+ };
+
+ Ok(())
+ }
+}
+
+/// Properly destroys all the vulkan objects we have.
+impl<'a> std::ops::Drop for RenderingContext<'a> {
+ fn drop(&mut self) {
+ use core::ptr::read;
+ let _ = self.device.wait_idle();
+
+ unsafe {
+ // cells (semaphores, fences & command buffers)
+ for cell in self.cells.drain(..) {
+ cell.destroy(&self.device, &mut self.command_pool);
+ }
+
+ // images
+ for image in self.backbuffer.drain(..) {
+ self.device.destroy_image(image);
+ }
+
+ // image views
+ for image_view in self.image_views.drain(..) {
+ self.device.destroy_image_view(image_view);
+ }
+
+ // framebuffers
+ for framebuffer in self.framebuffers.drain(..) {
+ self.device.destroy_framebuffer(framebuffer);
+ }
+
+ // command pool
+ self.device
+ .destroy_command_pool(ManuallyDrop::into_inner(read(&self.command_pool)).into_raw());
+
+ // render pass
+ self.device
+ .destroy_render_pass(ManuallyDrop::into_inner(read(&self.render_pass)));
+
+ // swapchain
+ self.device
+ .destroy_swapchain(ManuallyDrop::into_inner(read(&self.swapchain)));
+
+ ManuallyDrop::drop(&mut self.queue_group);
+ ManuallyDrop::drop(&mut self.device);
+ ManuallyDrop::drop(&mut self.instance);
+ }
+ }
+} \ No newline at end of file
diff --git a/stockton-render/src/draw/frame.rs b/stockton-render/src/draw/frame.rs
new file mode 100644
index 0000000..d1e617c
--- /dev/null
+++ b/stockton-render/src/draw/frame.rs
@@ -0,0 +1,51 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Helper struct. Keeps the data for each frame 'in flight' seperate instead of linked lists.
+use hal::{Device as DeviceTrait};
+use hal::command::CommandBuffer;
+use hal::Graphics;
+use back::{Backend};
+use hal::pool::{CommandPool};
+
+
+/// Helper struct for a frame that can be in flight
+pub struct FrameCell {
+ /// How we ask the GPU to do work for us.
+ pub command_buffer: CommandBuffer<Backend, Graphics>,
+
+ /// Signalled once an image is acquired to draw on.
+ pub image_available: <Backend as hal::Backend>::Semaphore,
+
+ /// Signalled once the frame is done being drawn.
+ pub render_finished: <Backend as hal::Backend>::Semaphore,
+
+ /// Signalled once the frame is presented.
+ pub frame_presented: <Backend as hal::Backend>::Fence
+}
+
+impl FrameCell {
+ /// Safely deinitialises all the objects in this struct.
+ /// Use this instead of drop.
+ pub unsafe fn destroy(self, device: &<Backend as hal::Backend>::Device, command_pool: &mut CommandPool<Backend, Graphics>) {
+ // fences & semaphores
+ device.destroy_semaphore(self.image_available);
+ device.destroy_semaphore(self.render_finished);
+ device.destroy_fence(self.frame_presented);
+
+ // command buffer
+ command_pool.free(vec![self.command_buffer]);
+ }
+} \ No newline at end of file
diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs
new file mode 100644
index 0000000..0774501
--- /dev/null
+++ b/stockton-render/src/draw/mod.rs
@@ -0,0 +1,21 @@
+// Copyright (C) Oscar Shrimpton 2019
+
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Given 3D points and some camera information, renders to the screen.
+
+mod context;
+mod frame;
+
+pub use context::RenderingContext; \ No newline at end of file
diff --git a/stockton-render/src/error.rs b/stockton-render/src/error.rs
new file mode 100644
index 0000000..d46328d
--- /dev/null
+++ b/stockton-render/src/error.rs
@@ -0,0 +1,99 @@
+// Copyright (C) 2019 Oscar Shrimpton
+
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this program. If not, see <http://www.gnu.org/licenses/>.
+
+//! Error types
+
+/// An error encountered creating a rendering context.
+/// Falls into 3 main types:
+/// - Hardware - No suitable card usually
+/// - Sanity - Things that probably aren't true, likely indicating a deeper issue.
+/// These aren't guaranteed sanity issues, but they are weird issues.
+/// - Runtime - Things caused by runtime conditions, usually resource constraints.
+/// You can use the associated methods to get the group of one, which may be helpful for error reporting, etc.
+#[derive(Debug, Clone)]
+pub enum CreationError {
+
+ /// # Hardware
+ NoAdapter,
+ NoQueueFamily,
+ NoPhysicalDevice,
+
+ /// # Sanity
+ NoQueueGroup,
+ NoCommandQueues,
+ NoPresentModes,
+ NoCompositeAlphas,
+ NoImageFormats,
+ NoColor,
+
+ /// # Runtime
+ SwapchainFailed (hal::window::CreationError),
+ RenderPassFailed (hal::device::OutOfMemory),
+ CommandPoolFailed (hal::device::OutOfMemory),
+ SemaphoreFailed (hal::device::OutOfMemory),
+ FenceFailed (hal::device::OutOfMemory),
+ ImageViewFailed (hal::image::ViewError),
+ FramebufferFailed (hal::device::OutOfMemory)
+}
+
+impl CreationError {
+ /// Check if the error is (likely) a hardware error
+ pub fn is_hardware(&self) -> bool {
+ use self::CreationError::*;
+ match &self {
+ NoAdapter | NoQueueFamily | NoPhysicalDevice => true,
+ _ => false
+ }
+ }
+ /// Check if the error is (possibly) a sanity error.
+ pub fn is_sanity(&self) -> bool {
+ use self::CreationError::*;
+ match &self {
+ NoQueueGroup | NoCommandQueues | NoPresentModes |
+ NoCompositeAlphas | NoImageFormats | NoColor
+ => true,
+ _ => false
+ }
+ }
+ /// Check if the error is (likely) a runtime error.
+ pub fn is_runtime(&self) -> bool {
+ use self::CreationError::*;
+ match &self {
+ SwapchainFailed(_) | RenderPassFailed(_) |
+ CommandPoolFailed(_) | SemaphoreFailed(_) |
+ FenceFailed(_) | ImageViewFailed(_) |
+ FramebufferFailed(_) => true,
+ _ => false
+ }
+ }
+}
+
+/// An error encountered when rendering.
+/// Usually this is out of memory or something happened to the device/surface.
+/// You'll likely need to exit or create a new context.
+#[derive(Debug, Clone)]
+pub enum FrameError {
+ /// Error getting the image from the swapchain
+ AcquisitionError (hal::window::AcquireError),
+
+ /// Error waiting on the frame_presented fence.
+ FenceWaitError (hal::device::OomOrDeviceLost),
+
+ /// Error resetting the frame_presented fence.
+ FenceResetError (hal::device::OutOfMemory),
+
+ /// Error presenting the rendered frame.
+ PresentError (hal::window::PresentError)
+} \ No newline at end of file
diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs
index de98360..79de1ce 100644
--- a/stockton-render/src/lib.rs
+++ b/stockton-render/src/lib.rs
@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
-//! Renders a world to a window.
+//! Renders a world to a window.
//!
//! You'll need to pick a backend using features. You should only pick one.
//! On Linux & Windows, you should use vulkan.
@@ -39,30 +39,39 @@ extern crate gfx_hal as hal;
extern crate stockton_types;
extern crate winit;
+extern crate arrayvec;
+
+mod error;
+mod draw;
+
+use error::{CreationError, FrameError};
+use draw::RenderingContext;
+
use stockton_types::World;
use winit::Window;
-use back::{Instance};
-use back::{Backend};
-
use std::sync::{Arc, RwLock};
pub struct Renderer<'a> {
world: Arc<RwLock<World<'a>>>,
- instance: Instance,
- window: &'a Window,
- surface: <Backend as hal::Backend>::Surface
+ context: RenderingContext<'a>
}
-impl<'a> Renderer<'a> {
- pub fn new(world: Arc<RwLock<World<'a>>>, window: &'a Window) -> Renderer<'a> {
- let instance = Instance::create("stockton", 1);
- let surface = instance.create_surface(&window);
+impl<'a> Renderer<'a> {
+ /// Create a new Renderer.
+ /// This initialises all the vulkan context, etc needed.
+ pub fn new(world: Arc<RwLock<World<'a>>>, window: &'a Window) -> Result<Self, CreationError> {
+ let context = RenderingContext::new(window)?;
+
+ Ok(Renderer {
+ world, context
+ })
+ }
- Renderer {
- world, window, instance, surface
- }
+ /// Draw a frame of solid `color` (RGBA)
+ pub fn draw_clear(&mut self, color: [f32; 4]) -> Result<(), FrameError> {
+ self.context.draw_clear(color)
}
}