#![forbid(unsafe_code)]
use common::logging::macros::{debug, info};
use engine_core::app::{ApplicationHost, ApplicationHostBuildError, ApplicationHostError, WindowError};
use engine_renderer_api::{BoxedRenderer, RenderWindow, RendererFactory};
use engine_renderer_vulkan::renderer::{VulkanRendererBuilder, VulkanRendererError};
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ApplicationError {
#[error("renderer failed: {0}")]
Renderer(#[source] VulkanRendererError),
#[error("window failed: {0}")]
Window(#[from] WindowError),
#[error("application build failed: {0}")]
Build(#[from] ApplicationHostBuildError),
}
impl From<ApplicationHostError<VulkanRendererError>> for ApplicationError {
fn from(error: ApplicationHostError<VulkanRendererError>) -> Self {
match error {
ApplicationHostError::Renderer(error) => Self::Renderer(error),
ApplicationHostError::Window(error) => Self::Window(error),
ApplicationHostError::Build(error) => Self::Build(error),
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum RendererBackend {
#[default]
Auto,
Vulkan,
}
#[derive(Debug)]
pub struct Application {
host: RuntimeApplicationHost,
}
type RuntimeApplicationHost = ApplicationHost<RendererBackendSelector>;
#[derive(Debug, Default)]
pub struct ApplicationBuilder {
name: Option<String>,
renderer_backend: RendererBackend,
vsync: bool,
}
impl ApplicationBuilder {
#[must_use]
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn with_renderer_backend(mut self, backend: RendererBackend) -> Self {
self.renderer_backend = backend;
self
}
#[must_use]
pub fn with_vsync(mut self, on: bool) -> Self {
self.vsync = on;
self
}
pub fn build(self) -> Result<Application, ApplicationError> {
debug!("building runtime application with backend {:?}", self.renderer_backend);
let selector = RendererBackendSelector { renderer_backend: self.renderer_backend, vsync: self.vsync };
let mut builder = ApplicationHost::builder(selector);
if let Some(name) = self.name {
builder = builder.with_name(name);
}
Ok(Application { host: builder.build()? })
}
}
impl Application {
pub fn builder() -> ApplicationBuilder {
ApplicationBuilder::default()
}
pub fn name(&self) -> &str {
self.host.name()
}
pub fn run(self) -> Result<(), ApplicationError> {
self.host.run().map_err(Into::into)
}
}
#[derive(Clone, Copy, Debug)]
struct RendererBackendSelector {
renderer_backend: RendererBackend,
vsync: bool,
}
impl RendererFactory for RendererBackendSelector {
type Error = VulkanRendererError;
fn create_renderer(&mut self, window: &dyn RenderWindow) -> Result<BoxedRenderer<Self::Error>, Self::Error> {
match self.renderer_backend {
RendererBackend::Auto | RendererBackend::Vulkan => {
info!(
"selected renderer backend vulkan with {} policy",
if matches!(self.renderer_backend, RendererBackend::Auto) {
"auto"
} else {
"forced"
}
);
create_vulkan_renderer(window, self.vsync)
}
}
}
}
fn create_vulkan_renderer(window: &dyn RenderWindow, vsync: bool) -> Result<BoxedRenderer<VulkanRendererError>, VulkanRendererError> {
debug!("creating vulkan renderer");
let renderer = VulkanRendererBuilder::default().with_vsync(vsync).build(window)?;
Ok(Box::new(renderer))
}