| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use darkfi_serial::{
- async_trait, AsyncEncodable, AsyncWrite, Decodable, Encodable, FutAsyncWriteExt,
- SerialDecodable, SerialEncodable, VarInt,
- };
- #[cfg(target_os = "android")]
- use miniquad::native::egl;
- use miniquad::{
- conf, window, Bindings, BufferSource, BufferType, BufferUsage, EventHandler, KeyCode, KeyMods,
- MouseButton, PassAction, Pipeline, RenderingBackend, TextureFormat, TextureKind, TextureParams,
- TextureWrap, TouchPhase, UniformType,
- };
- use std::{
- collections::{HashMap, HashSet},
- fs::File,
- io::Write,
- path::PathBuf,
- sync::{
- atomic::{AtomicU32, Ordering},
- Arc,
- },
- };
- pub mod anim;
- use anim::{Frame as AnimFrame, GfxSeqAnim};
- mod api;
- pub use api::{
- EpochIndex, GraphicsMethod, ManagedBuffer, ManagedBufferPtr, ManagedSeqAnim, ManagedSeqAnimPtr,
- ManagedTexture, ManagedTexturePtr, RenderApi, Renderer, RendererSync,
- };
- mod ev;
- pub use ev::{
- GraphicsEventCharSub, GraphicsEventKeyDownSub, GraphicsEventKeyUpSub,
- GraphicsEventMouseButtonDownSub, GraphicsEventMouseButtonUpSub, GraphicsEventMouseMoveSub,
- GraphicsEventMouseWheelSub, GraphicsEventPublisher, GraphicsEventPublisherPtr,
- GraphicsEventResizeSub, GraphicsEventTouchSub,
- };
- mod favico;
- mod prune;
- use prune::PruneMethodHeap;
- mod linalg;
- pub use linalg::{Dimension, Point, Rectangle, Segment, Vector};
- mod shader;
- mod trax;
- use trax::get_trax;
- use crate::{
- prop::{BatchGuardId, PropertyAtomicGuard},
- scene::{Pimpl, SceneNodePtr},
- util::unixtime,
- GOD,
- };
- // This is very noisy so suppress output by default
- const DEBUG_RENDER: bool = false;
- const DEBUG_GFXAPI: bool = false;
- const DEBUG_TRAX: bool = false;
- #[macro_export]
- macro_rules! gfxtag {
- ($s:expr) => {{
- Some($s)
- }};
- }
- pub use crate::gfxtag;
- pub type DebugTag = Option<&'static str>;
- macro_rules! d { ($($arg:tt)*) => { debug!(target: "gfx", $($arg)*); } }
- macro_rules! t { ($($arg:tt)*) => { trace!(target: "gfx", $($arg)*); } }
- #[cfg(target_os = "android")]
- pub fn get_window_size_filename() -> PathBuf {
- crate::android::get_appdata_path().join("window_size")
- }
- #[cfg(not(target_os = "android"))]
- pub fn get_window_size_filename() -> PathBuf {
- dirs::cache_dir().unwrap().join("darkfi/app/window_size")
- }
- #[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
- #[repr(C)]
- pub struct Vertex {
- pub pos: [f32; 2],
- pub color: [f32; 4],
- pub uv: [f32; 2],
- }
- impl Vertex {
- pub fn pos(&self) -> Point {
- self.pos.into()
- }
- pub fn set_pos(&mut self, pos: &Point) {
- self.pos = pos.as_arr();
- }
- }
- pub type TextureId = u32;
- pub type BufferId = u32;
- pub type AnimId = u32;
- #[derive(Clone, Debug)]
- pub struct DrawMesh {
- pub vertex_buffer: ManagedBufferPtr,
- pub index_buffer: ManagedBufferPtr,
- pub textures: Option<Vec<ManagedTexturePtr>>,
- pub num_elements: i32,
- }
- impl DrawMesh {
- fn compile(
- self,
- textures: &HashMap<TextureId, miniquad::TextureId>,
- buffers: &HashMap<BufferId, miniquad::BufferId>,
- debug_str: &'static str,
- ) -> GfxDrawMesh {
- let vertex_buffer_id = self.vertex_buffer.id;
- let index_buffer_id = self.index_buffer.id;
- let _buffers_keep_alive = [self.vertex_buffer, self.index_buffer];
- let textures = match self.textures {
- Some(gfx_textures) => {
- let mut compiled = Vec::with_capacity(gfx_textures.len());
- for gfx_texture in gfx_textures {
- compiled.push(Self::get_texture(textures, gfx_texture, debug_str));
- }
- Some(compiled)
- }
- None => None,
- };
- GfxDrawMesh {
- vertex_buffer: Self::get_buffer(buffers, vertex_buffer_id, debug_str),
- index_buffer: Self::get_buffer(buffers, index_buffer_id, debug_str),
- _buffers_keep_alive,
- textures,
- num_elements: self.num_elements,
- }
- }
- fn get_texture(
- textures: &HashMap<TextureId, miniquad::TextureId>,
- gfx_texture: ManagedTexturePtr,
- debug_str: &'static str,
- ) -> (ManagedTexturePtr, miniquad::TextureId) {
- let gfx_texture_id = gfx_texture.id;
- let Some(_mq_texture_id) = textures.get(&gfx_texture_id) else {
- panic!("Missing texture ID={gfx_texture_id} debug={debug_str}")
- };
- (gfx_texture, textures[&gfx_texture_id])
- }
- fn get_buffer(
- buffers: &HashMap<BufferId, miniquad::BufferId>,
- gfx_buffer_id: BufferId,
- debug_str: &'static str,
- ) -> miniquad::BufferId {
- let Some(mq_buffer_id) = buffers.get(&gfx_buffer_id) else {
- panic!("Missing buffer ID={gfx_buffer_id} debug={debug_str}")
- };
- *mq_buffer_id
- }
- }
- impl Encodable for DrawMesh {
- fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
- let mut len = 0;
- len += self.vertex_buffer.id.encode(s)?;
- len += self.vertex_buffer.epoch.encode(s)?;
- len += self.vertex_buffer.tag.encode(s)?;
- len += self.vertex_buffer.buftype.encode(s)?;
- len += self.index_buffer.id.encode(s)?;
- len += self.index_buffer.epoch.encode(s)?;
- len += self.index_buffer.tag.encode(s)?;
- len += self.index_buffer.buftype.encode(s)?;
- match &self.textures {
- Some(texs) => {
- len += 1u8.encode(s)?;
- len += VarInt(texs.len() as u64).encode(s)?;
- for t in texs {
- len += t.id.encode(s)?;
- len += t.epoch.encode(s)?;
- len += t.tag.encode(s)?;
- }
- }
- None => {
- len += 0u8.encode(s)?;
- }
- }
- len += self.num_elements.encode(s)?;
- Ok(len)
- }
- }
- #[derive(Clone, Copy, Debug, SerialEncodable)]
- pub enum GraphicPipeline {
- RGB,
- YUV,
- }
- #[async_trait]
- impl AsyncEncodable for DrawMesh {
- async fn encode_async<W: AsyncWrite + Unpin + Send>(
- &self,
- _: &mut W,
- ) -> std::io::Result<usize> {
- Ok(0)
- }
- }
- #[derive(Debug, Clone)]
- pub enum DrawInstruction {
- SetScale(f32),
- Move(Point),
- SetPos(Point),
- ApplyView(Rectangle),
- Draw(DrawMesh),
- Animation(ManagedSeqAnimPtr),
- EnableDebug,
- SetPipeline(GraphicPipeline),
- Overlay(Vec<DrawInstruction>),
- }
- impl DrawInstruction {
- fn compile(
- self,
- textures: &HashMap<TextureId, miniquad::TextureId>,
- buffers: &HashMap<BufferId, miniquad::BufferId>,
- debug_str: &'static str,
- ) -> GfxDrawInstruction {
- match self {
- Self::SetScale(scale) => GfxDrawInstruction::SetScale(scale),
- Self::Move(off) => GfxDrawInstruction::Move(off),
- Self::SetPos(pos) => GfxDrawInstruction::SetPos(pos),
- Self::ApplyView(view) => GfxDrawInstruction::ApplyView(view),
- Self::Draw(mesh) => {
- GfxDrawInstruction::Draw(mesh.compile(textures, buffers, debug_str))
- }
- Self::Animation(anim) => GfxDrawInstruction::Animation(anim),
- Self::EnableDebug => GfxDrawInstruction::EnableDebug,
- Self::SetPipeline(pipeline) => GfxDrawInstruction::SetPipeline(pipeline),
- Self::Overlay(instrs) => {
- let compiled =
- instrs.into_iter().map(|i| i.compile(textures, buffers, debug_str)).collect();
- GfxDrawInstruction::Overlay(compiled)
- }
- }
- }
- }
- #[derive(Clone, Debug, Default)]
- pub struct DrawCall {
- pub instrs: Vec<DrawInstruction>,
- pub dcs: Vec<DcId>,
- pub z_index: u32,
- pub debug_str: &'static str,
- }
- impl DrawCall {
- pub fn new(
- instrs: Vec<DrawInstruction>,
- dcs: Vec<DcId>,
- z_index: u32,
- debug_str: &'static str,
- ) -> Self {
- Self { instrs, dcs, z_index, debug_str }
- }
- fn compile(
- self,
- textures: &HashMap<TextureId, miniquad::TextureId>,
- buffers: &HashMap<BufferId, miniquad::BufferId>,
- timest: Timestamp,
- ) -> GfxDrawCall {
- GfxDrawCall {
- instrs: self
- .instrs
- .into_iter()
- .map(|i| i.compile(textures, buffers, self.debug_str))
- .collect(),
- dcs: self.dcs,
- z_index: self.z_index,
- timest,
- }
- }
- }
- #[derive(Clone, Debug)]
- struct GfxDrawMesh {
- vertex_buffer: miniquad::BufferId,
- index_buffer: miniquad::BufferId,
- /// Keeps the buffers alive for the duration of this draw call
- _buffers_keep_alive: [ManagedBufferPtr; 2],
- textures: Option<Vec<(ManagedTexturePtr, miniquad::TextureId)>>,
- num_elements: i32,
- }
- #[derive(Debug, Clone)]
- enum GfxDrawInstruction {
- SetScale(f32),
- Move(Point),
- SetPos(Point),
- ApplyView(Rectangle),
- Draw(GfxDrawMesh),
- Animation(ManagedSeqAnimPtr),
- EnableDebug,
- SetPipeline(GraphicPipeline),
- Overlay(Vec<GfxDrawInstruction>),
- }
- #[derive(Clone, Debug)]
- struct GfxDrawCall {
- instrs: Vec<GfxDrawInstruction>,
- dcs: Vec<DcId>,
- z_index: u32,
- timest: Timestamp,
- }
- struct OverlayDefer {
- scale: f32,
- pos: Point,
- instrs: Vec<GfxDrawInstruction>,
- }
- struct RenderContext<'a> {
- ctx: &'a mut Box<dyn RenderingBackend>,
- draw_calls: &'a HashMap<DcId, GfxDrawCall>,
- uniforms_data: [u8; 128],
- white_texture: miniquad::TextureId,
- loaded_pipelines: &'a [Pipeline; 2],
- scale: f32,
- view: Rectangle,
- cursor: Point,
- gfx_pipeline: GraphicPipeline,
- anims: &'a mut HashMap<AnimId, GfxSeqAnim>,
- overlays: Vec<OverlayDefer>,
- }
- impl<'a> RenderContext<'a> {
- fn draw(&mut self) {
- if DEBUG_RENDER {
- let screen_size = miniquad::window::screen_size();
- d!("RenderContext::draw() [screen_size={screen_size:?}]");
- }
- if DEBUG_TRAX {
- get_trax().lock().set_curr(0);
- }
- self.draw_call(&self.draw_calls[&0], 0, DEBUG_RENDER);
- if DEBUG_RENDER {
- d!("RenderContext::draw() [DONE]");
- }
- // View should be reset now so draw overlays
- self.draw_overlays();
- }
- fn draw_overlays(&mut self) {
- let (screen_w, screen_h) = miniquad::window::screen_size();
- let overlays = std::mem::take(&mut self.overlays);
- for overlay in overlays {
- self.view = Rectangle::new(0., 0., screen_w, screen_h);
- self.scale = overlay.scale;
- self.view.w /= self.scale;
- self.view.h /= self.scale;
- self.apply_view();
- self.cursor = overlay.pos;
- self.apply_model();
- for instr in overlay.instrs {
- match instr {
- GfxDrawInstruction::Move(off) => {
- self.cursor += off;
- self.apply_model();
- }
- GfxDrawInstruction::Draw(mesh) => {
- if DEBUG_RENDER {
- d!(" draw_overlay({mesh:?})");
- }
- let images = match &mesh.textures {
- Some(texs) => texs.iter().map(|(_, tex_id)| *tex_id).collect(),
- None => vec![self.white_texture],
- };
- let bindings = Bindings {
- vertex_buffers: vec![mesh.vertex_buffer],
- index_buffer: mesh.index_buffer,
- images,
- };
- self.ctx.apply_bindings(&bindings);
- self.ctx.draw(0, mesh.num_elements, 1);
- }
- _ => unimplemented!(),
- }
- }
- }
- }
- fn apply_view(&mut self) {
- // Actual physical view
- let view = self.view * self.scale;
- let (_, screen_height) = window::screen_size();
- let view_x = view.x.round() as i32;
- let view_y = screen_height - (view.y + view.h);
- let view_y = view_y.round() as i32;
- let view_w = view.w.round() as i32;
- let view_h = view.h.round() as i32;
- // OpenGL does not like negative values here
- if view_w <= 0 || view_h <= 0 {
- return
- }
- if DEBUG_RENDER {
- d!("=> viewport {view_x} {view_y} {view_w} {view_h}");
- }
- self.ctx.apply_viewport(view_x, view_y, view_w, view_h);
- self.ctx.apply_scissor_rect(view_x, view_y, view_w, view_h);
- }
- fn apply_model(&mut self) {
- let off_x = self.cursor.x / self.view.w;
- let off_y = self.cursor.y / self.view.h;
- let scale_w = 1. / self.view.w;
- let scale_h = 1. / self.view.h;
- let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
- glam::Mat4::from_scale(glam::Vec3::new(scale_w, scale_h, 1.));
- let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
- self.uniforms_data[64..].copy_from_slice(&data);
- self.ctx.apply_uniforms_from_bytes(self.uniforms_data.as_ptr(), self.uniforms_data.len());
- }
- fn draw_call(&mut self, draw_call: &GfxDrawCall, mut indent: u32, mut is_debug: bool) {
- let ws = if is_debug { " ".repeat(indent as usize * 4) } else { String::new() };
- let old_scale = self.scale;
- let old_view = self.view;
- let old_cursor = self.cursor;
- let old_pipeline = self.gfx_pipeline;
- for (idx, instr) in draw_call.instrs.iter().enumerate() {
- if DEBUG_TRAX {
- get_trax().lock().set_instr(idx);
- }
- match instr {
- GfxDrawInstruction::SetScale(scale) => {
- self.scale = *scale;
- self.view.w /= self.scale;
- self.view.h /= self.scale;
- if is_debug {
- d!("{ws}set_scale({scale})");
- }
- }
- GfxDrawInstruction::Move(off) => {
- self.cursor += *off;
- if is_debug {
- d!(
- "{ws}move({off:?}) cursor={:?}, scale={}, view={:?}",
- self.cursor,
- self.scale,
- self.view
- );
- }
- self.apply_model();
- }
- GfxDrawInstruction::SetPos(pos) => {
- self.cursor = old_cursor + *pos;
- if is_debug {
- d!(
- "{ws}set_pos({pos:?}) cursor={:?}, scale={}, view={:?}",
- self.cursor,
- self.scale,
- self.view
- );
- }
- self.apply_model();
- }
- GfxDrawInstruction::ApplyView(view) => {
- // Adjust view relative to cursor
- self.view = *view + self.cursor + old_view.pos();
- // We could just skip drawing when clipping rect isn't visible
- // using an is_visible flag.
- match self.view.clip(&old_view) {
- Some(clipped) => self.view = clipped,
- None => self.view = Rectangle::zero(),
- }
- // Cursor resets within the view
- self.cursor = Point::zero();
- if is_debug {
- d!("{ws}apply_view({view:?}) scale={}, view={:?}", self.scale, self.view);
- }
- self.apply_view();
- self.apply_model();
- }
- GfxDrawInstruction::Draw(mesh) => {
- if is_debug {
- d!("{ws}draw({mesh:?})");
- }
- let images = match &mesh.textures {
- Some(texs) => texs.iter().map(|(_, tex_id)| *tex_id).collect(),
- None => vec![self.white_texture],
- };
- let bindings = Bindings {
- vertex_buffers: vec![mesh.vertex_buffer],
- index_buffer: mesh.index_buffer,
- images,
- };
- self.ctx.apply_bindings(&bindings);
- self.ctx.draw(0, mesh.num_elements, 1);
- }
- GfxDrawInstruction::Animation(anim) => {
- let gfx_anim = self.anims.get_mut(&anim.id).unwrap();
- gfx_anim.is_visible = true;
- if let Some(dc) = gfx_anim.tick() {
- self.draw_call(&dc, indent + 1, is_debug);
- }
- }
- GfxDrawInstruction::EnableDebug => {
- if !is_debug {
- indent = 0;
- }
- is_debug = true;
- d!("Frame start");
- }
- GfxDrawInstruction::SetPipeline(pipeline) => {
- self.gfx_pipeline = *pipeline;
- let pipeline_idx = *pipeline as usize;
- assert!(pipeline_idx < self.loaded_pipelines.len());
- self.ctx.apply_pipeline(&self.loaded_pipelines[pipeline_idx]);
- if is_debug {
- d!("{ws}set_pipeline({pipeline:?})");
- }
- }
- GfxDrawInstruction::Overlay(instrs) => {
- let pos = self.view.pos() / self.scale + self.cursor;
- self.overlays.push(OverlayDefer {
- scale: self.scale,
- pos,
- instrs: instrs.clone(),
- });
- }
- }
- }
- let mut draw_calls: Vec<_> =
- draw_call.dcs.iter().map(|key| (key, &self.draw_calls[key])).collect();
- draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
- for (dc_key, dc) in draw_calls {
- if DEBUG_TRAX {
- get_trax().lock().set_curr(*dc_key);
- }
- if is_debug {
- d!("{ws}drawcall {dc_key}");
- }
- self.draw_call(dc, indent + 1, is_debug);
- }
- self.scale = old_scale;
- if is_debug {
- d!("{ws}Frame close: cursor={old_cursor:?}, view={old_view:?}");
- }
- self.view = old_view;
- self.apply_view();
- self.cursor = old_cursor;
- self.gfx_pipeline = old_pipeline;
- let pipeline_idx = self.gfx_pipeline as usize;
- assert!(pipeline_idx < self.loaded_pipelines.len());
- self.ctx.apply_pipeline(&self.loaded_pipelines[pipeline_idx]);
- self.apply_model();
- }
- }
- type Timestamp = u64;
- type DcId = u64;
- struct Stage {
- ctx: Box<dyn RenderingBackend>,
- #[cfg(target_os = "android")]
- libegl: egl::LibEgl,
- loaded_pipelines: [Pipeline; 2],
- white_texture: miniquad::TextureId,
- draw_calls: HashMap<DcId, GfxDrawCall>,
- pending_batches: HashMap<BatchGuardId, Vec<GraphicsMethod>>,
- /// When dropping batches, we add to this set so that we keep track
- /// of the internal state's correctness.
- dropped_batches: Box<HashSet<BatchGuardId>>,
- textures: Box<HashMap<TextureId, miniquad::TextureId>>,
- buffers: Box<HashMap<BufferId, miniquad::BufferId>>,
- anims: Box<HashMap<AnimId, GfxSeqAnim>>,
- epoch: EpochIndex,
- method_recv: async_channel::Receiver<(EpochIndex, GraphicsMethod)>,
- event_pub: GraphicsEventPublisherPtr,
- renderer: Renderer,
- pruner: PruneMethodHeap,
- screen_state: ScreenState,
- /// Cached window node, looked up lazily on first touch event
- window_node: Option<SceneNodePtr>,
- }
- impl Stage {
- pub fn new() -> Self {
- if DEBUG_TRAX {
- get_trax().lock().clear();
- }
- let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
- let god = GOD.get().unwrap();
- // Start a new epoch. This is a brand new UI run.
- let renderer = god.renderer.clone();
- let epoch = renderer.next_epoch();
- // This will start the app to start. Needed since we cannot get window size for init
- // until window is created.
- god.start_app(epoch);
- let method_recv = god.method_recv.clone();
- let event_pub = god.event_pub.clone();
- let white_texture = ctx.new_texture_from_rgba8(1, 1, &[255, 255, 255, 255]);
- let anims: HashMap<AnimId, GfxSeqAnim> = HashMap::new();
- let rgb_pipeline = shader::create_rgb_pipeline(&mut ctx);
- let yuv_pipeline = shader::create_yuv_pipeline(&mut ctx);
- #[cfg(target_os = "android")]
- let libegl = egl::LibEgl::try_load().expect("Cant load LibEGL");
- let mut self_ = Stage {
- ctx,
- #[cfg(target_os = "android")]
- libegl,
- loaded_pipelines: [rgb_pipeline, yuv_pipeline],
- white_texture,
- draw_calls: HashMap::from([(
- 0,
- GfxDrawCall { instrs: vec![], dcs: vec![], z_index: 0, timest: 0 },
- )]),
- pending_batches: HashMap::new(),
- dropped_batches: Box::new(HashSet::new()),
- textures: Box::new(HashMap::new()),
- buffers: Box::new(HashMap::new()),
- anims: Box::new(anims),
- epoch,
- method_recv,
- event_pub,
- renderer,
- pruner: PruneMethodHeap::new(epoch),
- screen_state: ScreenState::On,
- window_node: None,
- };
- self_.pruner.textures = &*self_.textures as *const _;
- self_.pruner.buffers = &*self_.buffers as *const _;
- self_.pruner.anims = &*self_.anims as *const _;
- self_.pruner.dropped_batches = &mut *self_.dropped_batches as *mut _;
- self_
- }
- fn process_method(&mut self, mut method: GraphicsMethod) {
- //d!("Received method: {method:?}");
- match &mut method {
- GraphicsMethod::NewTexture((width, height, data, fmt, gtex_id, _)) => {
- self.method_new_texture(*width, *height, data, *fmt, *gtex_id)
- }
- GraphicsMethod::DeleteTexture((gtex_id, _)) => self.method_delete_texture(*gtex_id),
- GraphicsMethod::NewVertexBuffer((verts, gbuff_id, _)) => {
- self.method_new_vertex_buffer(verts, *gbuff_id)
- }
- GraphicsMethod::NewIndexBuffer((indices, gbuff_id, _)) => {
- self.method_new_index_buffer(indices, *gbuff_id)
- }
- GraphicsMethod::DeleteBuffer((gbuff_id, _, _)) => self.method_delete_buffer(*gbuff_id),
- GraphicsMethod::NewSeqAnim { id, frames_len, oneshot, tag: _ } => {
- self.method_new_anim(*id, *frames_len, *oneshot)
- }
- GraphicsMethod::UpdateSeqAnim { id, frame_idx, frame, tag: _ } => {
- self.method_update_anim(*id, *frame_idx, frame.clone())
- }
- GraphicsMethod::DeleteSeqAnim((ganim_id, _)) => self.method_delete_anim(*ganim_id),
- GraphicsMethod::ReplaceGfxDrawCalls { batch_id, ref mut dcs } => {
- match batch_id {
- Some(bid) => {
- //let debug_strs: Vec<_> = dcs.iter().map(|(_, dc)| dc.debug_str).collect();
- //t!("Commit dc to {bid}: {debug_strs:?}");
- if self.dropped_batches.contains(&bid) {
- t!("Discarding ReplaceGfxDrawCalls from dropped {bid}");
- return
- }
- let Some(batch) = self.pending_batches.get_mut(&bid) else {
- panic!("unknown batch {bid}")
- };
- let method = std::mem::take(&mut method);
- batch.push(method);
- }
- None => {
- // Process immediately without batching
- let timest = unixtime();
- let dcs = std::mem::take(dcs);
- self.method_replace_draw_calls(timest, dcs);
- }
- }
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- GraphicsMethod::StartBatch { batch_id, tag } => {
- if DEBUG_GFXAPI {
- t!("Start batch {batch_id}: {tag:?}");
- }
- if !self.pending_batches.insert(*batch_id, vec![]).is_none() {
- panic!("batch {batch_id} already open!")
- }
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- GraphicsMethod::EndBatch { batch_id, timest } => {
- if self.dropped_batches.remove(batch_id) {
- if DEBUG_GFXAPI {
- t!("End batch {batch_id} was dropped");
- }
- return
- }
- if DEBUG_GFXAPI {
- t!("End batch {batch_id}");
- }
- let Some(batch) = self.pending_batches.remove(batch_id) else {
- panic!("unknown batch {batch_id}")
- };
- for mut method in batch {
- match &mut method {
- GraphicsMethod::ReplaceGfxDrawCalls { batch_id: _, dcs } => {
- let dcs = std::mem::take(dcs);
- self.method_replace_draw_calls(*timest, dcs)
- }
- _ => panic!("unexpected method in batch!"),
- }
- }
- }
- GraphicsMethod::Noop => panic!("noop"),
- }
- }
- pub(self) fn method_new_texture(
- &mut self,
- width: u16,
- height: u16,
- data: &Vec<u8>,
- format: TextureFormat,
- gfx_texture_id: TextureId,
- ) {
- let fmt_size = format.size(width as u32, height as u32) as usize;
- assert_eq!(
- fmt_size,
- data.len(),
- "Texture data size mismatch for ID={gfx_texture_id}: \
- expected {fmt_size}, got {} for {width}x{height} {:?}",
- data.len(),
- format
- );
- let texture = self.ctx.new_texture_from_data_and_format(
- data,
- TextureParams {
- kind: TextureKind::Texture2D,
- format,
- width: width as _,
- height: height as _,
- wrap: TextureWrap::Clamp,
- min_filter: miniquad::FilterMode::Linear,
- mag_filter: miniquad::FilterMode::Linear,
- mipmap_filter: miniquad::MipmapFilterMode::None,
- allocate_mipmaps: false,
- sample_count: 1,
- },
- );
- if DEBUG_GFXAPI {
- d!("Invoked method: new_texture({width}, {height}, ..., {gfx_texture_id}) -> {texture:?}");
- //d!("Invoked method: new_texture({}, {}, ..., {}) -> {:?}\n{}",
- // width, height, gfx_texture_id, texture,
- // ansi_texture(width as usize, height as usize, &data));
- }
- if let Some(_) = self.textures.insert(gfx_texture_id, texture) {
- if DEBUG_TRAX {
- get_trax().lock().put_stat(2);
- }
- panic!("Duplicate texture ID={gfx_texture_id} detected!");
- }
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- pub(self) fn method_delete_texture(&mut self, gfx_texture_id: TextureId) {
- let Some(texture) = self.textures.remove(&gfx_texture_id) else {
- if DEBUG_TRAX {
- get_trax().lock().put_stat(2);
- }
- panic!("unknown texture {gfx_texture_id}")
- };
- if DEBUG_GFXAPI {
- d!("Invoked method: delete_texture({gfx_texture_id} => {texture:?})");
- }
- self.ctx.delete_texture(texture);
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- pub(self) fn method_new_vertex_buffer(&mut self, verts: &[Vertex], gfx_buffer_id: BufferId) {
- let buffer = self.ctx.new_buffer(
- BufferType::VertexBuffer,
- BufferUsage::Immutable,
- BufferSource::slice(verts),
- );
- if DEBUG_GFXAPI {
- d!("Invoked method: new_vertex_buffer(..., {gfx_buffer_id}) -> {buffer:?}");
- //debug!(target: "gfx", "Invoked method: new_vertex_buffer({:?}, {}) -> {:?}",
- // verts, gfx_buffer_id, buffer);
- }
- if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
- if DEBUG_TRAX {
- get_trax().lock().put_stat(2);
- }
- panic!("Duplicate vertex buffer ID={gfx_buffer_id} detected!")
- }
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- pub(self) fn method_new_index_buffer(&mut self, indices: &[u16], gfx_buffer_id: BufferId) {
- let buffer = self.ctx.new_buffer(
- BufferType::IndexBuffer,
- BufferUsage::Immutable,
- BufferSource::slice(&indices),
- );
- if DEBUG_GFXAPI {
- d!("Invoked method: new_index_buffer({gfx_buffer_id}) -> {buffer:?}");
- //debug!(target: "gfx", "Invoked method: new_index_buffer({:?}, {}) -> {:?}",
- // indices, gfx_buffer_id, buffer);
- }
- if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
- if DEBUG_TRAX {
- get_trax().lock().put_stat(2);
- }
- panic!("Duplicate index buffer ID={gfx_buffer_id} detected!")
- }
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- pub(self) fn method_delete_buffer(&mut self, gfx_buffer_id: BufferId) {
- let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
- if DEBUG_TRAX {
- get_trax().lock().put_stat(2);
- }
- panic!("unknown buffer {gfx_buffer_id}");
- };
- if DEBUG_GFXAPI {
- d!("Invoked method: delete_buffer({gfx_buffer_id} => {buffer:?})");
- }
- self.ctx.delete_buffer(buffer);
- if DEBUG_TRAX {
- get_trax().lock().put_stat(0);
- }
- }
- pub(self) fn method_new_anim(&mut self, gfx_anim_id: AnimId, frames_len: usize, oneshot: bool) {
- if DEBUG_GFXAPI {
- d!("Invoked method: new_anim({gfx_anim_id}, {frames_len}, {oneshot})");
- }
- if let Some(_) = self.anims.insert(gfx_anim_id, GfxSeqAnim::new(frames_len, oneshot)) {
- panic!("Duplicate anim ID={gfx_anim_id} detected!");
- }
- }
- pub(self) fn method_update_anim(
- &mut self,
- gfx_anim_id: AnimId,
- frame_idx: usize,
- frame: AnimFrame,
- ) {
- let Some(anim) = self.anims.get_mut(&gfx_anim_id) else {
- panic!("couldn't find anim {gfx_anim_id}");
- };
- if DEBUG_GFXAPI {
- d!("Invoked method: update_anim({gfx_anim_id}[{frame_idx}] => {frame:?})");
- }
- anim.set(frame_idx, frame, &self.textures, &self.buffers);
- }
- pub(self) fn method_delete_anim(&mut self, gfx_anim_id: AnimId) {
- let Some(anim) = self.anims.remove(&gfx_anim_id) else {
- panic!("couldn't find anim {gfx_anim_id}");
- };
- if DEBUG_GFXAPI {
- d!("Invoked method: delete_anim({} => {:?})", gfx_anim_id, anim);
- }
- }
- fn method_replace_draw_calls(&mut self, batch_timest: Timestamp, dcs: Vec<(DcId, DrawCall)>) {
- if DEBUG_GFXAPI {
- d!("Invoked method: replace_draw_calls({:?})", dcs);
- }
- // Phase 1: Check for conflicts with newer batches
- // If any draw call in this batch belongs to an older batch than the existing one,
- // reject the entire batch to maintain atomicity
- for (key, _) in &dcs {
- if let Some(old_val) = self.draw_calls.get(key) {
- if old_val.timest > batch_timest {
- // Entire batch is stale, reject all
- t!("Rejected stale batch {batch_timest}: conflict with newer batch {} on {key}", old_val.timest);
- if DEBUG_TRAX {
- get_trax().lock().put_stat(3); // New stat: rejected batch
- }
- return;
- }
- }
- }
- // Phase 2: Apply entire batch atomically
- self.apply_draw_calls(batch_timest, dcs)
- }
- pub(self) fn apply_draw_calls(&mut self, batch_timest: Timestamp, dcs: Vec<(DcId, DrawCall)>) {
- for (key, val) in dcs {
- let val = val.compile(&self.textures, &self.buffers, batch_timest);
- // Insert/replace draw call
- self.draw_calls.insert(key, val);
- if DEBUG_TRAX {
- get_trax().lock().put_stat(1); // Success
- }
- }
- }
- fn trax_method(&self, epoch: EpochIndex, method: &GraphicsMethod) {
- let mut trax = get_trax().lock();
- match method {
- GraphicsMethod::NewTexture((_, _, _, _, gtex_id, tag)) => {
- trax.put_tex(epoch, *gtex_id, *tag);
- }
- GraphicsMethod::DeleteTexture((gtex_id, tag)) => {
- trax.del_tex(epoch, *gtex_id, *tag);
- }
- GraphicsMethod::NewVertexBuffer((verts, gbuff_id, tag)) => {
- trax.put_verts(epoch, verts.clone(), *gbuff_id, *tag, 0);
- }
- GraphicsMethod::NewIndexBuffer((idxs, gbuff_id, tag)) => {
- trax.put_idxs(epoch, idxs.clone(), *gbuff_id, *tag, 1);
- }
- GraphicsMethod::DeleteBuffer((gbuff_id, tag, buftype)) => {
- trax.del_buf(epoch, *gbuff_id, *tag, *buftype);
- }
- GraphicsMethod::NewSeqAnim { .. } => {
- //trax.put_idxs(epoch, idxs.clone(), *gbuff_id, *tag, 1);
- }
- GraphicsMethod::UpdateSeqAnim { .. } => {
- //trax.put_idxs(epoch, idxs.clone(), *gbuff_id, *tag, 1);
- }
- GraphicsMethod::DeleteSeqAnim(..) => {
- //trax.del_buf(epoch, *gbuff_id, *tag, *buftype);
- }
- GraphicsMethod::ReplaceGfxDrawCalls { batch_id, dcs } => {
- if let Some(bid) = batch_id {
- trax.put_dcs(epoch, *bid, dcs);
- }
- }
- GraphicsMethod::StartBatch { batch_id, tag } => {
- trax.put_start_batch(epoch, *batch_id, *tag);
- }
- GraphicsMethod::EndBatch { batch_id, timest: _ } => {
- trax.put_end_batch(epoch, *batch_id);
- }
- GraphicsMethod::Noop => panic!("noop"),
- };
- }
- fn egl_ctx_is_disabled(&self) -> bool {
- #[cfg(target_os = "android")]
- {
- let egl_ctx = unsafe { (self.libegl.eglGetCurrentContext)() };
- egl_ctx.is_null()
- }
- #[cfg(not(target_os = "android"))]
- false
- }
- #[instrument(skip_all, target = "gfx::process")]
- fn process_methods(&mut self) {
- // Process as many methods as we can
- while let Ok((epoch, method)) = self.method_recv.try_recv() {
- if DEBUG_TRAX {
- self.trax_method(epoch, &method);
- }
- if epoch < self.epoch {
- if DEBUG_TRAX {
- let mut trax = get_trax().lock();
- trax.put_stat(1);
- trax.flush();
- }
- // Discard old rubbish
- t!(
- "Discard method with old epoch: {epoch} curr: {} [method={method:?}]",
- self.epoch
- );
- continue
- }
- assert_eq!(epoch, self.epoch);
- self.process_method(method);
- if DEBUG_TRAX {
- get_trax().lock().flush();
- }
- }
- }
- #[instrument(skip_all, target = "gfx::pruner")]
- fn prime_screen(&mut self) {
- let methods = self.pruner.recv_all();
- assert!(self.pending_batches.is_empty());
- // Process all cached methods by the pruner from while the screen was off.
- for method in methods {
- // Stale methods will be dropped by pruner, so they will not be caught by trax
- // while the screen is off.
- if DEBUG_TRAX {
- self.trax_method(self.epoch, &method);
- }
- // We discard batches here but process_method uses them so implement this
- // workaround.
- match method {
- GraphicsMethod::NewTexture(_) |
- GraphicsMethod::DeleteTexture(_) |
- GraphicsMethod::NewVertexBuffer(_) |
- GraphicsMethod::NewIndexBuffer(_) |
- GraphicsMethod::DeleteBuffer(_) |
- GraphicsMethod::NewSeqAnim { .. } |
- GraphicsMethod::UpdateSeqAnim { .. } |
- GraphicsMethod::DeleteSeqAnim(_) => self.process_method(method),
- GraphicsMethod::ReplaceGfxDrawCalls { .. } |
- GraphicsMethod::StartBatch { .. } |
- GraphicsMethod::EndBatch { .. } => {
- panic!("unsupported pruned methods should be dropped!")
- }
- GraphicsMethod::Noop => panic!("noop"),
- }
- if DEBUG_TRAX {
- get_trax().lock().flush();
- }
- }
- // Trigger a full screen redraw by sending a resize event
- let (width, height) = miniquad::window::screen_size();
- self.event_pub.notify_resize(Dimension::from([width, height]));
- }
- fn close_pending_batches(&mut self) {
- // Immediately apply any pending batches when the screen is switched off
- let batch_ids: Vec<_> = self.pending_batches.keys().cloned().collect();
- if !batch_ids.is_empty() {
- t!("Force closing pending batches: {batch_ids:?}");
- }
- for batch_id in batch_ids {
- self.process_method(GraphicsMethod::EndBatch { batch_id, timest: unixtime() });
- if !self.dropped_batches.insert(batch_id) {
- panic!("dropped batch {batch_id} already exits!");
- }
- }
- assert!(self.pending_batches.is_empty());
- }
- }
- #[derive(Copy, Clone, Debug, PartialEq)]
- enum ScreenState {
- // Screen is on as normal
- On,
- // First update since screen has gone off
- SwitchOff,
- // Subsequent updates after SwitchOff state
- Off,
- // First update when screen is on but before draw has been called
- ReadyOn,
- // First update and draw has been called
- PrimedOn,
- // Second update ready to process pruned buffered allocs
- SwitchOn,
- }
- impl ScreenState {
- fn update(&mut self, egl_is_disabled: bool) {
- use ScreenState::*;
- *self = if egl_is_disabled {
- match self {
- On | ReadyOn | PrimedOn | SwitchOn => SwitchOff,
- SwitchOff => Off,
- Off => Off,
- }
- } else {
- match self {
- SwitchOff | Off => ReadyOn,
- ReadyOn => PrimedOn,
- PrimedOn => SwitchOn,
- SwitchOn => On,
- On => On,
- }
- };
- }
- }
- impl EventHandler for Stage {
- fn update(&mut self) {
- // todo: trax is all messed up in this func
- let old_screen_state = self.screen_state;
- self.screen_state.update(self.egl_ctx_is_disabled());
- if self.screen_state != old_screen_state {
- d!("Switching screen state {old_screen_state:?} => {:?}", self.screen_state);
- }
- match self.screen_state {
- ScreenState::SwitchOff => {
- self.close_pending_batches();
- // Screen is off so collect all methods into the pruner
- self.pruner.drain(&self.method_recv);
- }
- ScreenState::Off => {
- assert!(self.pending_batches.is_empty());
- // Screen is off so collect all methods into the pruner
- self.pruner.drain(&self.method_recv);
- }
- ScreenState::ReadyOn => {
- // We actually want to skip draining the prune queue the first time so
- // miniquad draw() actually gets a chance to be called first.
- // Otherwise we will see a black screen for a sec or so while update() is running.
- }
- ScreenState::PrimedOn => {
- self.prime_screen();
- }
- ScreenState::SwitchOn => {
- // This should have been cleared in previous PrimedOn state
- let methods = self.pruner.recv_all();
- assert!(methods.is_empty());
- self.process_methods();
- }
- ScreenState::On => {
- self.process_methods();
- }
- }
- }
- fn draw(&mut self) {
- self.ctx.begin_default_pass(PassAction::clear_color(0., 0., 0., 1.));
- // Apply default RGB pipeline
- self.ctx.apply_pipeline(&self.loaded_pipelines[GraphicPipeline::RGB as usize]);
- // This will make the top left (0, 0) and the bottom right (1, 1)
- // Default is (-1, 1) -> (1, -1)
- let proj = glam::Mat4::from_translation(glam::Vec3::new(-1., 1., 0.)) *
- glam::Mat4::from_scale(glam::Vec3::new(2., -2., 1.));
- let mut uniforms_data = [0u8; 128];
- let data: [u8; 64] = unsafe { std::mem::transmute_copy(&proj) };
- uniforms_data[0..64].copy_from_slice(&data);
- //let data: [u8; 64] = unsafe { std::mem::transmute_copy(&model) };
- //uniforms_data[64..].copy_from_slice(&data);
- assert_eq!(128, 2 * UniformType::Mat4.size());
- let (screen_w, screen_h) = miniquad::window::screen_size();
- // Mark all anims as invisible
- for (_, anim) in self.anims.iter_mut() {
- anim.is_visible = false;
- }
- let mut render_ctx = RenderContext {
- ctx: &mut self.ctx,
- draw_calls: &self.draw_calls,
- uniforms_data,
- white_texture: self.white_texture,
- loaded_pipelines: &self.loaded_pipelines,
- scale: 1.,
- view: Rectangle::from([0., 0., screen_w, screen_h]),
- cursor: Point::zero(),
- gfx_pipeline: GraphicPipeline::RGB,
- anims: &mut self.anims,
- overlays: vec![],
- };
- render_ctx.draw();
- self.ctx.end_render_pass();
- self.ctx.commit_frame();
- }
- fn resize_event(&mut self, width: f32, height: f32) {
- t!("resize_event({width}, {height})");
- let filename = get_window_size_filename();
- if let Some(parent) = filename.parent() {
- let _ = std::fs::create_dir_all(parent);
- }
- if let Ok(mut file) = File::create(filename) {
- (width as i32).encode(&mut file).unwrap();
- (height as i32).encode(&mut file).unwrap();
- }
- self.event_pub.notify_resize(Dimension::from([width, height]));
- }
- fn key_down_event(&mut self, keycode: KeyCode, mods: KeyMods, repeat: bool) {
- self.event_pub.notify_key_down(keycode, mods, repeat);
- }
- fn key_up_event(&mut self, keycode: KeyCode, mods: KeyMods) {
- self.event_pub.notify_key_up(keycode, mods);
- }
- fn char_event(&mut self, chr: char, mods: KeyMods, repeat: bool) {
- self.event_pub.notify_char(chr, mods, repeat);
- }
- fn mouse_button_down_event(&mut self, button: MouseButton, x: f32, y: f32) {
- let pos = Point::from([x, y]);
- self.event_pub.notify_mouse_btn_down(button, pos);
- }
- fn mouse_button_up_event(&mut self, button: MouseButton, x: f32, y: f32) {
- let pos = Point::from([x, y]);
- self.event_pub.notify_mouse_btn_up(button, pos);
- }
- fn mouse_motion_event(&mut self, x: f32, y: f32) {
- let pos = Point::from([x, y]);
- self.event_pub.notify_mouse_move(pos);
- }
- fn mouse_wheel_event(&mut self, x: f32, y: f32) {
- let pos = Point::from([x, y]);
- self.event_pub.notify_mouse_wheel(pos);
- }
- /// The id corresponds to multi-touch. Multiple touch events have different ids.
- fn touch_event(&mut self, phase: TouchPhase, id: u64, x: f32, y: f32) {
- let pos = Point::from([x, y]);
- // Lazy cache window on first touch event
- if self.window_node.is_none() {
- let god = GOD.get().unwrap();
- self.window_node = god.app.sg_root.lookup_node("/window");
- }
- // Clone window_node to avoid borrow conflict with RenderApiSync
- let window_node = self.window_node.clone();
- // Create RenderApiSync for direct graphics operations
- let renderer_sync = RendererSync::new(self);
- // Direct call to Window's handle_touch_event_sync
- if let Some(window_node) = &window_node {
- match window_node.pimpl() {
- Pimpl::Window(win) => {
- if win.handle_touch_sync(&renderer_sync, phase, id, pos) {
- return
- }
- }
- _ => panic!(),
- }
- }
- drop(renderer_sync);
- self.event_pub.notify_touch(phase, id, pos);
- }
- fn quit_requested_event(&mut self) {
- debug!(target: "gfx", "quit requested");
- let god = GOD.get().unwrap();
- god.stop_app();
- }
- }
- pub fn run_gui(linux_backend: miniquad::conf::LinuxBackend) {
- let mut window_width = 1024;
- let mut window_height = 768;
- if let Ok(mut file) = File::open(get_window_size_filename()) {
- window_width = Decodable::decode(&mut file).unwrap();
- window_height = Decodable::decode(&mut file).unwrap();
- }
- debug!(target: "gfx", "Window size {window_width} x {window_height}");
- let mut conf = miniquad::conf::Conf {
- window_title: "DarkFi".to_string(),
- window_width,
- window_height,
- high_dpi: true,
- window_resizable: true,
- platform: miniquad::conf::Platform {
- linux_backend,
- #[cfg(target_os = "android")]
- blocking_event_loop: true,
- #[cfg(target_os = "android")]
- sleep_interval_ms: Some(40),
- android_panic_hook: false,
- ..Default::default()
- },
- icon: Some(miniquad::conf::Icon {
- small: favico::SMALL,
- medium: favico::MEDIUM,
- big: favico::BIG,
- }),
- ..Default::default()
- };
- let metal = std::env::args().nth(1).as_deref() == Some("metal");
- conf.platform.apple_gfx_api =
- if metal { conf::AppleGfxApi::Metal } else { conf::AppleGfxApi::OpenGl };
- miniquad::start(conf, || Box::new(Stage::new()));
- }
|