use std::any::Any; use std::cell::{RefCell, UnsafeCell}; use std::fmt::format; use std::mem; use std::pin::Pin; use std::ptr::slice_from_raw_parts_mut; use imgui::{DrawData, TextureId, Ui}; use itertools::all; use lazy_static::lazy_static; use deko3d::{ make_texture_handle, Barrier, BlendFactor, BlendOp, BlendState, CmdBuf, CmdBufMaker, ColorMask, ColorState, ColorWriteState, CopyBuf, DepthStencilState, DeviceMaker, Face, Filter, Image, ImageDescriptor, ImageFlags, ImageFormat, ImageLayout, ImageLayoutMaker, ImageRect, ImageView, InvalidateFlags, MemBlock, MemBlockFlags, MemBlockMaker, MipFilter, Primitive, QueueFlags, QueueMaker, RasterizerState, ResHandle, Sampler, SamplerDescriptor, Scissor, Shader, ShaderMaker, Stage, StageFlag, SwapchainMaker, Viewport, VtxAttribSize, VtxAttribState, VtxAttribType, VtxBufferState, WrapMode, DK_CMDMEM_ALIGNMENT, DK_MEMBLOCK_ALIGNMENT, DK_SHADER_CODE_ALIGNMENT, DK_SHADER_CODE_UNUSABLE_SIZE, DK_UNIFORM_BUF_ALIGNMENT, }; use crate::common::{Color, Rect}; use crate::framework::backend::{ Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData, }; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; use crate::framework::gamepad; use crate::framework::gamepad::{Axis, Button, GamepadType}; use crate::framework::graphics::BlendMode; use crate::framework::util::field_offset; use crate::game::shared_game_state::SharedGameState; use crate::game::Game; mod nx { type NWindow = std::ffi::c_void; pub const HID_PAD_NO1: u64 = 1 << 0; pub const HID_PAD_NO2: u64 = 1 << 1; pub const HID_PAD_NO3: u64 = 1 << 2; pub const HID_PAD_NO4: u64 = 1 << 3; pub const HID_PAD_NO5: u64 = 1 << 4; pub const HID_PAD_NO6: u64 = 1 << 5; pub const HID_PAD_NO7: u64 = 1 << 6; pub const HID_PAD_NO8: u64 = 1 << 7; pub const HID_PAD_OTHER: u64 = 1 << 0x10; pub const HID_PAD_HANDHELD: u64 = 1 << 0x20; pub const HID_PAD_STYLE_FULL_KEY: u32 = 1 << 0; pub const HID_PAD_STYLE_HANDHELD: u32 = 1 << 1; pub const HID_PAD_STYLE_JOY_DUAL: u32 = 1 << 2; pub const HID_PAD_STYLE_JOY_LEFT: u32 = 1 << 3; pub const HID_PAD_STYLE_JOY_RIGHT: u32 = 1 << 4; pub const HID_PAD_STYLE_GC: u32 = 1 << 5; pub const HID_PAD_STYLE_PALMA: u32 = 1 << 6; pub const HID_PAD_STYLE_LARK: u32 = 1 << 7; pub const HID_PAD_STYLE_HANDHELD_LARK: u32 = 1 << 8; pub const HID_PAD_STYLE_LUCIA: u32 = 1 << 9; pub const HID_PAD_STYLE_LAGON: u32 = 1 << 10; pub const HID_PAD_STYLE_LAGER: u32 = 1 << 11; pub const HID_PAD_STYLE_SYSTEM_EXT: u32 = 1 << 29; pub const HID_PAD_STYLE_SYSTEM: u32 = 1 << 30; pub const HID_PAD_STYLE_SET_FULL_CTRL: u32 = HID_PAD_STYLE_FULL_KEY | HID_PAD_STYLE_HANDHELD | HID_PAD_STYLE_JOY_DUAL; pub const HID_PAD_STYLE_SET_STANDARD: u32 = HID_PAD_STYLE_SET_FULL_CTRL | HID_PAD_STYLE_JOY_LEFT | HID_PAD_STYLE_JOY_RIGHT; extern "C" { pub fn nwindowGetDefault() -> *mut NWindow; fn padInitializeWithMask(pad_state: *mut PadState, mask: u64); fn padConfigureInput(max_players: u32, style_set: u32); fn padUpdate(pad_state: *mut PadState); } #[repr(C)] #[derive(Copy, Clone)] pub struct HidAnalogStickState { pub x: i32, pub y: i32, } #[repr(C)] #[derive(Copy, Clone)] pub struct PadState { pub id_mask: u8, pub active_id_mask: u8, pub read_handheld: bool, pub active_handheld: bool, pub style_set: u32, pub attributes: u32, pub buttons_cur: u64, pub buttons_old: u64, pub sticks: [HidAnalogStickState; 2], pub gc_triggers: [u32; 2], } #[repr(C)] #[derive(Copy, Clone)] pub struct PadRepeater { pub button_mask: u64, pub counter: i32, pub delay: u16, pub repeat: u16, } pub fn pad_configure(max_players: u32, style_set: u32) { unsafe { padConfigureInput(max_players, style_set); } } impl PadState { pub fn initialize(mask: u64) -> Self { let mut state = Self { id_mask: 0, active_id_mask: 0, read_handheld: false, active_handheld: false, style_set: 0, attributes: 0, buttons_cur: 0, buttons_old: 0, sticks: [HidAnalogStickState { x: 0, y: 0 }; 2], gc_triggers: [0; 2], }; unsafe { padInitializeWithMask(&mut state, mask); } state } pub fn initialize_any() -> Self { Self::initialize(0x1000100FF) } pub fn initialize_default() -> Self { Self::initialize(HID_PAD_NO1 | HID_PAD_HANDHELD) } pub fn update(&mut self) { unsafe { padUpdate(self); } } pub fn is_connected(&self) -> bool { self.id_mask != 0 } pub fn get_buttons_down(&self) -> u64 { self.buttons_cur & !self.buttons_old } pub fn get_buttons_up(&self) -> u64 { !self.buttons_cur & self.buttons_old } } #[repr(C)] #[derive(Copy, Clone)] pub struct Service { session: u32, own_handle: u32, object_id: u32, pointer_buffer_size: u16, } #[repr(C)] #[derive(Copy, Clone)] pub struct Event { revent: u32, wevent: u32, auto_clear: bool, } #[repr(C)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum AppletId { None = 0, Application = 1, } #[repr(C)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum LibAppletMode { AllForeground = 0, Background = 1, NoUi = 2, BackgroundIndirect = 3, AllForegroundInitiallyHidden = 4, } #[repr(C)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum LibAppletExitReason { Normal = 0, Canceled = 1, Abnormal = 2, Unexpected = 10, } #[repr(C)] #[derive(Copy, Clone)] pub struct WebCommonTLVStorage { data: [u8; 0x2000], } #[repr(C)] #[derive(Copy, Clone)] pub struct AppletHolder { pub s: Service, pub StateChangedEvent: Event, pub PopInteractiveOutDataEvent: Event, pub mode: LibAppletMode, pub layer_handle: u64, pub creating_self: bool, pub exitreason: LibAppletExitReason, } #[repr(C)] #[derive(Copy, Clone)] pub struct WebCommonConfig { arg: WebCommonTLVStorage, applet_id: AppletId, version: u32, holder: AppletHolder, } extern "C" { pub fn webPageCreate(config: *mut WebCommonConfig, url: *const std::ffi::c_char) -> u32; pub fn webConfigSetWhitelist(config: *mut WebCommonConfig, whitelist: *const std::ffi::c_char) -> u32; pub fn webConfigShow(config: *mut WebCommonConfig, out: *mut u32) -> u32; } impl WebCommonConfig { pub fn new() -> WebCommonConfig { unsafe { std::mem::zeroed() } } } extern "C" { pub fn romfsMountDataStorageFromProgram(program_id: u64, name: *const std::ffi::c_char) -> u32; pub fn romfsMountFromCurrentProcess(name: *const std::ffi::c_char) -> u32; } } pub struct HorizonBackend; impl HorizonBackend { pub fn new() -> GameResult> { Ok(Box::new(HorizonBackend)) } } impl Backend for HorizonBackend { fn create_event_loop(&self, _ctx: &Context) -> GameResult> { nx::pad_configure(8, nx::HID_PAD_STYLE_SET_STANDARD); let mut gamepads = [ nx::PadState::initialize_default(), nx::PadState::initialize(nx::HID_PAD_NO2), nx::PadState::initialize(nx::HID_PAD_NO3), nx::PadState::initialize(nx::HID_PAD_NO4), nx::PadState::initialize(nx::HID_PAD_NO5), nx::PadState::initialize(nx::HID_PAD_NO6), nx::PadState::initialize(nx::HID_PAD_NO7), nx::PadState::initialize(nx::HID_PAD_NO8), ]; for pad in gamepads.iter_mut() { pad.update(); } Ok(Box::new(HorizonEventLoop { gamepads, active: [false; 8] })) } } pub struct HorizonEventLoop { gamepads: [nx::PadState; 8], active: [bool; 8], } const GAMEPAD_KEYMAP: [Button; 16] = [ Button::South, Button::East, Button::West, Button::North, Button::LeftStick, Button::RightStick, Button::LeftShoulder, Button::RightShoulder, Button::LeftShoulder, Button::RightShoulder, Button::Back, Button::Start, Button::DPadLeft, Button::DPadUp, Button::DPadRight, Button::DPadDown, ]; const fn align(size: u32, align: u32) -> u32 { (size + align - 1) & !(align - 1) } impl HorizonEventLoop { fn gamepad_update(&mut self, state: &SharedGameState, ctx: &mut Context) { for (id, pad) in self.gamepads.iter_mut().enumerate() { pad.update(); let connected = pad.is_connected(); if connected != self.active[id] { if connected { // connected log::info!("Gamepad {} connected", id); let axis_sensitivity = state.settings.get_gamepad_axis_sensitivity(id as u32); ctx.gamepad_context.add_gamepad(HorizonGamepad::new(id as u32), axis_sensitivity); ctx.gamepad_context.set_gamepad_type(id as u32, GamepadType::NintendoSwitchJoyConPair); } else { // disconnected log::info!("Gamepad {} disconnected", id); ctx.gamepad_context.remove_gamepad(id as u32); } self.active[id] = connected; } } for (id, pad) in self.gamepads.iter().enumerate() { if !pad.is_connected() { continue; } let buttons_down = pad.get_buttons_down(); let buttons_up = pad.get_buttons_up(); for i in 0..GAMEPAD_KEYMAP.len() { let button = GAMEPAD_KEYMAP[i]; let mask = 1 << i; if i == 8 { ctx.gamepad_context.set_axis_value(id as u32, Axis::TriggerLeft, if buttons_down & mask != 0 { 1.0 } else { 0.0 }); continue; } else if i == 9 { ctx.gamepad_context.set_axis_value(id as u32, Axis::TriggerRight, if buttons_down & mask != 0 { 1.0 } else { 0.0 }); continue; } if buttons_down & mask != 0 { ctx.gamepad_context.set_button(id as u32, button, true); } if buttons_up & mask != 0 { ctx.gamepad_context.set_button(id as u32, button, false); } } let analog_x = pad.sticks[0].x as f64 / 32768.0; let analog_y = -pad.sticks[0].y as f64 / 32768.0; ctx.gamepad_context.set_axis_value(id as u32, Axis::LeftX, (analog_x).clamp(-1.0, 1.0)); ctx.gamepad_context.set_axis_value(id as u32, Axis::LeftY, (analog_y).clamp(-1.0, 1.0)); ctx.gamepad_context.set_axis_value(id as u32, Axis::RightX, (analog_x).clamp(-1.0, 1.0)); ctx.gamepad_context.set_axis_value(id as u32, Axis::RightY, (analog_y).clamp(-1.0, 1.0)); ctx.gamepad_context.update_axes(id as u32); } } } impl BackendEventLoop for HorizonEventLoop { fn run(&mut self, game: &mut Game, ctx: &mut Context) { let state_ref = unsafe { &mut *game.state.get() }; let scale = 1.0; ctx.screen_size = (854.0 * scale, 480.0 * scale); state_ref.handle_resize(ctx).unwrap(); loop { self.gamepad_update(state_ref, ctx); game.update(ctx).unwrap(); if state_ref.shutdown { log::info!("Shutting down..."); break; } if state_ref.next_scene.is_some() { mem::swap(&mut game.scene, &mut state_ref.next_scene); state_ref.next_scene = None; game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap(); game.loops = 0; state_ref.frame_time = 0.0; } game.draw(ctx).unwrap(); } } fn new_renderer(&self, ctx: *mut Context) -> GameResult> { let mut imgui = imgui::Context::create(); let ctx = unsafe { &mut *ctx }; imgui.io_mut().display_size = [ctx.screen_size.0, ctx.screen_size.1]; imgui.fonts().build_alpha8_texture(); let device = DeviceMaker::new().create(); Deko3DRenderer::new(device, imgui) } } pub struct HorizonGamepad { pub id: u32, } impl HorizonGamepad { fn new(id: u32) -> Box { Box::new(HorizonGamepad { id }) } } impl BackendGamepad for HorizonGamepad { fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult { Ok(()) } fn instance_id(&self) -> u32 { self.id } } lazy_static! { static ref VERTEX_ATTRIB_STATE: [VtxAttribState; 3] = [ *VtxAttribState::new() .set_offset(field_offset::(|v| &v.position) as u16) .set_size(VtxAttribSize::_2x32) .set_type(VtxAttribType::Float), *VtxAttribState::new() .set_offset(field_offset::(|v| &v.uv) as u16) .set_size(VtxAttribSize::_2x32) .set_type(VtxAttribType::Float), *VtxAttribState::new() .set_offset(field_offset::(|v| &v.color) as u16) .set_size(VtxAttribSize::_4x8) .set_type(VtxAttribType::Unorm), ]; static ref VERTEX_BUFFER_STATE: [VtxBufferState; 1] = [VtxBufferState { stride: mem::size_of::() as u32, divisor: 0 }]; } struct Deko3DVertexBuffer { buffer: deko3d::MemBlock, capacity: usize, // those two are in bytes allocated: usize, } impl Deko3DVertexBuffer { pub fn new(device: &deko3d::Device) -> GameResult { let capacity = 2 * 16 * DK_MEMBLOCK_ALIGNMENT; let buffer = MemBlockMaker::new(device, capacity as u32) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); Ok(Deko3DVertexBuffer { buffer, capacity: capacity as usize, allocated: 0 }) } pub fn transfer(&mut self, vertices: &[VertexData], device: &deko3d::Device) -> GameResult<()> { let allocated = vertices.len() * mem::size_of::(); let size = allocated.max(16 * DK_MEMBLOCK_ALIGNMENT as usize); let size = align(2 * size as u32, DK_MEMBLOCK_ALIGNMENT) as usize; if size > u32::MAX as usize { return Err(GameError::ResourceLoadError("Vertex buffer too large".to_string())); } if size > self.capacity { self.buffer = MemBlockMaker::new(device, size as u32) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); self.capacity = size; } unsafe { (self.buffer.get_cpu_addr() as *mut VertexData).copy_from_nonoverlapping(vertices.as_ptr(), vertices.len()); } self.allocated = allocated; Ok(()) } } struct Deko3DShader { ubo_mem_block: deko3d::MemBlock, code_mem_block: deko3d::MemBlock, vtx_shader: deko3d::Shader, frag_shader: deko3d::Shader, data: UBO, } impl Deko3DShader { pub fn new( device: &deko3d::Device, vertex_shader_binary: &[u8], fragment_shader_binary: &[u8], ) -> GameResult { let ubo_size = mem::size_of::(); let ubo_size = align(ubo_size as u32, DK_MEMBLOCK_ALIGNMENT) as usize; let ubo_mem_block = MemBlockMaker::new(device, ubo_size as u32) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); let vtx_binary_len = align(vertex_shader_binary.len() as u32, DK_SHADER_CODE_ALIGNMENT) as usize; let frag_binary_len = align(fragment_shader_binary.len() as u32, DK_SHADER_CODE_ALIGNMENT) as usize; let code_size = vtx_binary_len + frag_binary_len + DK_SHADER_CODE_UNUSABLE_SIZE as usize; let code_size = align(code_size as u32, DK_MEMBLOCK_ALIGNMENT) as usize; let code_mem_block = MemBlockMaker::new(&device, code_size as u32) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached | MemBlockFlags::Code) .create(); unsafe { let buf = code_mem_block.get_cpu_addr() as *mut u8; let vtx_code = buf; let frag_code = buf.add(vtx_binary_len); vtx_code.copy_from_nonoverlapping(vertex_shader_binary.as_ptr(), vertex_shader_binary.len()); frag_code.copy_from_nonoverlapping(fragment_shader_binary.as_ptr(), fragment_shader_binary.len()); } let mut vtx_shader = Shader::new(); let mut frag_shader = Shader::new(); ShaderMaker::new(&code_mem_block, 0).initialize(&mut vtx_shader); ShaderMaker::new(&code_mem_block, vtx_binary_len as u32).initialize(&mut frag_shader); Ok(Deko3DShader { ubo_mem_block, code_mem_block, vtx_shader, frag_shader, data: Default::default() }) } pub fn update_uniforms(&mut self, data: UBO) { self.data = data; } pub fn bind(&self, cmd_buf: &deko3d::CmdBuf) { cmd_buf.bind_shaders(StageFlag::GraphicsMask, &[&self.vtx_shader, &self.frag_shader]); cmd_buf.bind_uniform_buffer(Stage::Vertex, 0, self.ubo_mem_block.get_gpu_addr(), self.ubo_mem_block.get_size()); cmd_buf.push_constants( self.ubo_mem_block.get_gpu_addr(), self.ubo_mem_block.get_size(), 0, mem::size_of::() as u32, &self.data as *const _ as *const std::ffi::c_void, ); cmd_buf.bind_vtx_attrib_state(&*VERTEX_ATTRIB_STATE); cmd_buf.bind_vtx_buffer_state(&*VERTEX_BUFFER_STATE); } } #[repr(C)] #[derive(Clone, Copy)] struct VertUBO { proj_mtx: [[f32; 4]; 4], } impl Default for VertUBO { fn default() -> Self { VertUBO { proj_mtx: [[0.0; 4]; 4] } } } struct Deko3DTextureDesc { image: deko3d::ImageDescriptor, sampler: deko3d::SamplerDescriptor, } pub struct Deko3DTexture { dimensions: (u16, u16), desc_memory: deko3d::MemBlock, memory: deko3d::MemBlock, image: deko3d::Image, vertices: Vec, vbo: Deko3DVertexBuffer, renderer: *mut Deko3DRenderer, } impl Deko3DTexture { unsafe fn renderer<'a, 'b: 'a>(&'a self) -> &'b mut Deko3DRenderer { unsafe { &mut *self.renderer } } } impl BackendTexture for Deko3DTexture { fn dimensions(&self) -> (u16, u16) { self.dimensions } fn add(&mut self, command: SpriteBatchCommand) { let (width, height) = self.dimensions; let (tex_scale_x, tex_scale_y) = (1.0 / width as f32, 1.0 / height as f32); match command { SpriteBatchCommand::DrawRect(src, dest) => { let vertices = [ VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.left, dest.top), uv: (src.left * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.bottom), uv: (src.right * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, ]; self.vertices.extend_from_slice(&vertices); } SpriteBatchCommand::DrawRectFlip(mut src, dest, flip_x, flip_y) => { if flip_x { std::mem::swap(&mut src.left, &mut src.right); } if flip_y { std::mem::swap(&mut src.top, &mut src.bottom); } let vertices = [ VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.left, dest.top), uv: (src.left * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color: (255, 255, 255, 255), }, VertexData { position: (dest.right, dest.bottom), uv: (src.right * tex_scale_x, src.bottom * tex_scale_y), color: (255, 255, 255, 255), }, ]; self.vertices.extend_from_slice(&vertices); } SpriteBatchCommand::DrawRectTinted(src, dest, color) => { let color = color.to_rgba(); let vertices = [ VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color, }, VertexData { position: (dest.left, dest.top), uv: (src.left * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color, }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.right, dest.bottom), uv: (src.right * tex_scale_x, src.bottom * tex_scale_y), color, }, ]; self.vertices.extend_from_slice(&vertices); } SpriteBatchCommand::DrawRectFlipTinted(mut src, dest, flip_x, flip_y, color) => { if flip_x { std::mem::swap(&mut src.left, &mut src.right); } if flip_y { std::mem::swap(&mut src.top, &mut src.bottom); } let color = color.to_rgba(); let vertices = [ VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color, }, VertexData { position: (dest.left, dest.top), uv: (src.left * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.left, dest.bottom), uv: (src.left * tex_scale_x, src.bottom * tex_scale_y), color, }, VertexData { position: (dest.right, dest.top), uv: (src.right * tex_scale_x, src.top * tex_scale_y), color, }, VertexData { position: (dest.right, dest.bottom), uv: (src.right * tex_scale_x, src.bottom * tex_scale_y), color, }, ]; self.vertices.extend_from_slice(&vertices); } } } fn clear(&mut self) { self.vertices.clear(); } fn draw(&mut self) -> GameResult<()> { let renderer = unsafe { self.renderer() }; self.vbo.transfer(&self.vertices, &renderer.device)?; let cmdbuf = &renderer.cmdbuf[renderer.slot as usize]; cmdbuf.bind_vtx_buffer(0, self.vbo.buffer.get_gpu_addr(), self.vbo.buffer.get_size()); let img_offset = field_offset::(|d| &d.image); let sampler_offset = field_offset::(|d| &d.sampler); let desc_gpu = self.desc_memory.get_gpu_addr(); cmdbuf.bind_sampler_descriptor_set(desc_gpu + sampler_offset as u64, 1); cmdbuf.bind_image_descriptor_set(desc_gpu + img_offset as u64, 1); cmdbuf.bind_textures(Stage::Fragment, 0, &[make_texture_handle(0, 0)]); renderer.texture_shader.update_uniforms(VertUBO { proj_mtx: renderer.curr_mtx }); renderer.texture_shader.bind(cmdbuf); cmdbuf.draw(Primitive::Triangles, self.vertices.len() as u32, 1, 0, 0); cmdbuf.barrier(Barrier::Fragments, InvalidateFlags::None); renderer.queue.submit_commands(cmdbuf.finish_list()); renderer.queue.wait_idle(); cmdbuf.clear(); Ok(()) } fn as_any(&self) -> &dyn Any { self } } const BUFFER_COUNT: u32 = 2; // double buffering pub struct Deko3DRenderer { device: deko3d::Device, queue: deko3d::Queue, fb_mem_block: deko3d::MemBlock, framebuffers: [deko3d::Image; BUFFER_COUNT as usize], swapchain: deko3d::Swapchain, depth_mem_block: deko3d::MemBlock, depthbuffer: deko3d::Image, cmdbuf_mem_block: [deko3d::MemBlock; BUFFER_COUNT as usize], cmdbuf: [deko3d::CmdBuf; BUFFER_COUNT as usize], cmdbuf_ctrl_mem_block: deko3d::MemBlock, cmdbuf_ctrl: deko3d::CmdBuf, vbo: Deko3DVertexBuffer, texture_shader: Deko3DShader, color_shader: Deko3DShader, curr_mtx: [[f32; 4]; 4], width: u32, height: u32, fb_width: u32, fb_height: u32, slot: i32, imgui: UnsafeCell, } impl Deko3DRenderer { fn new(device: deko3d::Device, imgui: imgui::Context) -> GameResult> { let fb_width = 854; let fb_height = 480; let queue = QueueMaker::new(&device).set_flags(QueueFlags::Graphics).create(); let mut depth_layout = ImageLayout::new(); ImageLayoutMaker::new(&device) .set_flags(ImageFlags::UsageRender | ImageFlags::HwCompression) .set_format(ImageFormat::Z24S8) .set_dimensions(fb_width, fb_height, 0) .initialize(&mut depth_layout); let depth_size = align(align(depth_layout.get_size() as u32, depth_layout.get_alignment()), DK_MEMBLOCK_ALIGNMENT); let depth_mem_block = MemBlockMaker::new(&device, depth_size).set_flags(MemBlockFlags::Image | MemBlockFlags::GpuCached).create(); let mut depthbuffer = Image::new(); depthbuffer.initialize(&depth_layout, &depth_mem_block, 0); let mut fb_layout: ImageLayout = ImageLayout::new(); ImageLayoutMaker::new(&device) .set_flags(ImageFlags::UsageRender | ImageFlags::UsagePresent | ImageFlags::HwCompression) .set_format(ImageFormat::RGBA8Unorm) .set_dimensions(fb_width, fb_height, 0) .initialize(&mut fb_layout); let mut framebuffers: [Image; BUFFER_COUNT as usize] = [Image::new(), Image::new()]; let fb_size = align(align(fb_layout.get_size() as u32, fb_layout.get_alignment()), DK_MEMBLOCK_ALIGNMENT); let fb_mem_block = MemBlockMaker::new(&device, framebuffers.len() as u32 * fb_size) .set_flags(MemBlockFlags::Image | MemBlockFlags::GpuCached) .create(); for (i, fb) in framebuffers.iter_mut().enumerate() { fb.initialize(&fb_layout, &fb_mem_block, i as u32 * fb_size); } let native_window = unsafe { nx::nwindowGetDefault() }; let swapchain = SwapchainMaker::new(&device, native_window, &framebuffers).create(); let cmd_mem_size = 16 * 1024; let cmdbuf_size = align(cmd_mem_size, DK_MEMBLOCK_ALIGNMENT); let cmdbuf_mem_block: [MemBlock; BUFFER_COUNT as usize] = [ MemBlockMaker::new(&device, cmdbuf_size) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(), MemBlockMaker::new(&device, cmdbuf_size) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(), ]; let cmdbuf: [CmdBuf; BUFFER_COUNT as usize] = [CmdBufMaker::new(&device).create(), CmdBufMaker::new(&device).create()]; for (i, cmdbuf) in cmdbuf.iter().enumerate() { cmdbuf.add_memory(&cmdbuf_mem_block[i], 0, cmd_mem_size); } let cmdbuf_ctrl_mem_block = MemBlockMaker::new(&device, cmdbuf_size) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); let cmdbuf_ctrl = CmdBufMaker::new(&device).create(); cmdbuf_ctrl.add_memory(&cmdbuf_ctrl_mem_block, 0, cmd_mem_size); let vbo = Deko3DVertexBuffer::new(&device)?; let texture_shader = Deko3DShader::::new( &device, include_bytes!("shaders/deko3d/vertex_basic.dksh"), include_bytes!("shaders/deko3d/fragment_textured.dksh"), )?; let color_shader = Deko3DShader::::new( &device, include_bytes!("shaders/deko3d/vertex_basic.dksh"), include_bytes!("shaders/deko3d/fragment_color.dksh"), )?; Ok(Box::new(Deko3DRenderer { device, queue, fb_mem_block, framebuffers, swapchain, depth_mem_block, depthbuffer, cmdbuf_mem_block, cmdbuf, cmdbuf_ctrl_mem_block, cmdbuf_ctrl, vbo, texture_shader, color_shader, curr_mtx: [[0.0; 4]; 4], width: fb_width, height: fb_height, fb_width, fb_height, slot: 0, imgui: UnsafeCell::new(imgui), })) } } impl BackendRenderer for Deko3DRenderer { fn renderer_name(&self) -> String { "deko3d".to_owned() } fn clear(&mut self, color: Color) { let cmdbuf = &self.cmdbuf[self.slot as usize]; cmdbuf.clear_color_float(0, ColorMask::RGBA, color.r, color.g, color.b, color.a); } fn present(&mut self) -> GameResult { let cmdbuf = &self.cmdbuf[self.slot as usize]; cmdbuf.barrier(Barrier::Fragments, InvalidateFlags::None); cmdbuf.discard_depth_stencil(); self.queue.submit_commands(cmdbuf.finish_list()); self.queue.wait_idle(); self.queue.present_image(&self.swapchain, self.slot); Ok(()) } fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult { self.slot = self.queue.acquire_image(&self.swapchain); let cmdbuf = &self.cmdbuf[self.slot as usize]; cmdbuf.clear(); let image_view = ImageView::new(&self.framebuffers[self.slot as usize]); let depth_view = ImageView::new(&self.depthbuffer); cmdbuf.bind_render_targets(&[&image_view], Some(&depth_view)); cmdbuf.set_viewports(0, &[Viewport { x: 0.0, y: 0.0, width, height, near: -1000.0, far: 1000.0 }]); cmdbuf.set_scissors(0, &[Scissor { x: 0, y: 0, width: width as u32, height: height as u32 }]); cmdbuf.clear_color_float(0, ColorMask::RGBA, 0.0, 0.0, 0.0, 1.0); cmdbuf.clear_depth_stencil(true, 1.0, 0xff, 0); cmdbuf.bind_rasterizer_state(&RasterizerState::new().set_cull_mode(Face::None)); cmdbuf.bind_color_state(&ColorState::new().set_blend_enable(0, true)); cmdbuf.bind_color_write_state(&ColorWriteState::new()); cmdbuf.bind_depth_stencil_state(&DepthStencilState::new().set_depth_test_enable(false)); cmdbuf.bind_blend_states(0, &[BlendState::new()]); self.curr_mtx = [ [2.0 / width, 0.0, 0.0, 0.0], [0.0, 2.0 / -height, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0], ]; Ok(()) } fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult> { let img_total = width as u32 * height as u32 * 4; let desc_memory = MemBlockMaker::new( &self.device, align(std::mem::size_of::() as u32, DK_MEMBLOCK_ALIGNMENT), ) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); let mut desc_cpu = unsafe { &mut *(desc_memory.get_cpu_addr() as *mut Deko3DTextureDesc) }; desc_cpu.sampler = SamplerDescriptor::new(); desc_cpu.image = ImageDescriptor::new(); let mut layout = ImageLayout::new(); ImageLayoutMaker::new(&self.device) .set_flags(ImageFlags::UsageRender | ImageFlags::BlockLinear) .set_format(ImageFormat::RGBA8Unorm) .set_dimensions(width as u32, height as u32, 0) .initialize(&mut layout); let memory = MemBlockMaker::new( &self.device, align(layout.get_size() as u32, DK_MEMBLOCK_ALIGNMENT.max(layout.get_alignment())), ) .set_flags(MemBlockFlags::Image | MemBlockFlags::GpuCached) .create(); let mut image = Image::new(); image.initialize(&layout, &memory, 0); desc_cpu.image.initialize(&ImageView::new(&image), false, false); desc_cpu.sampler.initialize( &Sampler::new().set_filter(Filter::Nearest, Filter::Nearest, MipFilter::None).set_wrap_mode( WrapMode::ClampToEdge, WrapMode::ClampToEdge, WrapMode::ClampToEdge, ), ); let vbo = Deko3DVertexBuffer::new(&self.device)?; Ok(Box::new(Deko3DTexture { dimensions: (width, height), desc_memory, memory, image, vertices: Vec::new(), vbo, renderer: self, })) } fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { let img_total = width as u32 * height as u32 * 4; let desc_memory = MemBlockMaker::new( &self.device, align(std::mem::size_of::() as u32, DK_MEMBLOCK_ALIGNMENT), ) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); let scratch_memory = MemBlockMaker::new(&self.device, align(img_total, DK_MEMBLOCK_ALIGNMENT)) .set_flags(MemBlockFlags::CpuUncached | MemBlockFlags::GpuCached) .create(); unsafe { let len = data.len().min(img_total as usize); (scratch_memory.get_cpu_addr() as *mut u8).copy_from_nonoverlapping(data.as_ptr(), len); } let mut desc_cpu = unsafe { &mut *(desc_memory.get_cpu_addr() as *mut Deko3DTextureDesc) }; desc_cpu.sampler = SamplerDescriptor::new(); desc_cpu.image = ImageDescriptor::new(); let desc_gpu = desc_memory.get_gpu_addr(); let img_offset = field_offset::(|d| &d.image); let sampler_offset = field_offset::(|d| &d.sampler); let mut layout = ImageLayout::new(); ImageLayoutMaker::new(&self.device) .set_flags(ImageFlags::UsageRender | ImageFlags::BlockLinear) .set_format(ImageFormat::RGBA8Unorm) .set_dimensions(width as u32, height as u32, 0) .initialize(&mut layout); let memory = MemBlockMaker::new( &self.device, align(layout.get_size() as u32, DK_MEMBLOCK_ALIGNMENT.max(layout.get_alignment())), ) .set_flags(MemBlockFlags::Image | MemBlockFlags::GpuCached) .create(); let mut image = Image::new(); image.initialize(&layout, &memory, 0); desc_cpu.image.initialize(&ImageView::new(&image), false, false); desc_cpu.sampler.initialize( &Sampler::new().set_filter(Filter::Nearest, Filter::Nearest, MipFilter::None).set_wrap_mode( WrapMode::ClampToEdge, WrapMode::ClampToEdge, WrapMode::ClampToEdge, ), ); let cmdbuf = &self.cmdbuf_ctrl; // let cmdbuf = &self.cmdbuf[self.slot as usize]; cmdbuf.bind_sampler_descriptor_set(desc_gpu + sampler_offset as u64, 1); cmdbuf.bind_image_descriptor_set(desc_gpu + img_offset as u64, 1); cmdbuf.copy_buffer_to_image( &CopyBuf { addr: scratch_memory.get_gpu_addr(), rowLength: 0, imageHeight: 0 }, &ImageView::new(&image), &ImageRect { x: 0, y: 0, z: 0, width: width as u32, height: height as u32, depth: 1 }, 0, ); cmdbuf.barrier(Barrier::None, InvalidateFlags::Descriptors); self.queue.submit_commands(cmdbuf.finish_list()); self.queue.wait_idle(); cmdbuf.clear(); let vbo = Deko3DVertexBuffer::new(&self.device)?; Ok(Box::new(Deko3DTexture { dimensions: (width, height), desc_memory, memory, image, vertices: Vec::new(), vbo, renderer: self, })) } fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult { let cmdbuf = &self.cmdbuf[self.slot as usize]; match blend { BlendMode::None => { cmdbuf.bind_blend_states(0, &[BlendState::new()]); } BlendMode::Add => { cmdbuf.bind_blend_states( 0, &[*BlendState::new() .set_src_color_blend_factor(BlendFactor::One) .set_dst_color_blend_factor(BlendFactor::One) .set_src_alpha_blend_factor(BlendFactor::One) .set_dst_alpha_blend_factor(BlendFactor::One)], ); } BlendMode::Alpha => { cmdbuf.bind_blend_states(0, &[BlendState::new()]); } BlendMode::Multiply => { cmdbuf.bind_blend_states( 0, &[*BlendState::new() .set_src_color_blend_factor(BlendFactor::Zero) .set_dst_color_blend_factor(BlendFactor::SrcColor) .set_src_alpha_blend_factor(BlendFactor::Zero) .set_dst_alpha_blend_factor(BlendFactor::SrcAlpha)], ); } } Ok(()) } fn set_render_target(&mut self, texture: Option<&Box>) -> GameResult { if let Some(texture) = texture { let deko_texture = texture .as_any() .downcast_ref::() .ok_or_else(|| GameError::RenderError("This texture was not created by deko3d backend.".to_string()))?; let width = deko_texture.dimensions.0 as f32; let height = deko_texture.dimensions.1 as f32; let cmdbuf = &self.cmdbuf[self.slot as usize]; let image_view = ImageView::new(&deko_texture.image); cmdbuf.bind_render_targets(&[&image_view], None); cmdbuf.set_viewports(0, &[Viewport { x: 0.0, y: 0.0, width, height, near: -1000.0, far: 1000.0 }]); cmdbuf.set_scissors(0, &[Scissor { x: 0, y: 0, width: width as u32, height: height as u32 }]); self.width = width as u32; self.height = height as u32; self.curr_mtx = [ [2.0 / width, 0.0, 0.0, 0.0], [0.0, 2.0 / -height, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0], ]; } else { let width = self.fb_width as f32; let height = self.fb_height as f32; let cmdbuf = &self.cmdbuf[self.slot as usize]; let image_view = ImageView::new(&self.framebuffers[self.slot as usize]); let depth_view = ImageView::new(&self.depthbuffer); cmdbuf.bind_render_targets(&[&image_view], Some(&depth_view)); cmdbuf.set_viewports(0, &[Viewport { x: 0.0, y: 0.0, width, height, near: -1000.0, far: 1000.0 }]); cmdbuf.set_scissors(0, &[Scissor { x: 0, y: 0, width: width as u32, height: height as u32 }]); self.width = self.fb_width; self.height = self.fb_height; self.curr_mtx = [ [2.0 / width, 0.0, 0.0, 0.0], [0.0, 2.0 / -height, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0], ]; } Ok(()) } fn draw_rect(&mut self, rect: Rect, color: Color) -> GameResult { let cmdbuf = &self.cmdbuf[self.slot as usize]; let color = color.to_rgba(); let uv = (0.0, 0.0); let vertices = [ VertexData { position: (rect.left as _, rect.bottom as _), uv, color }, VertexData { position: (rect.left as _, rect.top as _), uv, color }, VertexData { position: (rect.right as _, rect.top as _), uv, color }, VertexData { position: (rect.left as _, rect.bottom as _), uv, color }, VertexData { position: (rect.right as _, rect.top as _), uv, color }, VertexData { position: (rect.right as _, rect.bottom as _), uv, color }, ]; self.vbo.transfer(&vertices, &self.device); cmdbuf.bind_vtx_buffer(0, self.vbo.buffer.get_gpu_addr(), self.vbo.buffer.get_size()); self.color_shader.update_uniforms(VertUBO { proj_mtx: self.curr_mtx }); self.color_shader.bind(cmdbuf); cmdbuf.draw(Primitive::Triangles, vertices.len() as u32, 1, 0, 0); cmdbuf.barrier(Barrier::Fragments, InvalidateFlags::None); self.queue.submit_commands(cmdbuf.finish_list()); self.queue.wait_idle(); cmdbuf.clear(); Ok(()) } fn draw_outline_rect(&mut self, _rect: Rect, _line_width: usize, _color: Color) -> GameResult { Ok(()) } fn set_clip_rect(&mut self, rect: Option) -> GameResult { let width = self.width; let height = self.height; let cmdbuf = &self.cmdbuf[self.slot as usize]; if let Some(rect) = rect { let x = rect.left.max(0); let y = rect.top.max(0); let width = (rect.right - x).min(width as isize); let height = (rect.bottom - y).min(height as isize); let (x, y, width, height) = (x as u32, y as u32, width as u32, height as u32); cmdbuf.set_scissors(0, &[Scissor { x, y, width, height }]); } else { cmdbuf.set_scissors(0, &[Scissor { x: 0, y: 0, width, height }]); } Ok(()) } fn imgui(&self) -> GameResult<&mut imgui::Context> { unsafe { Ok(&mut *self.imgui.get()) } } fn imgui_texture_id(&self, _texture: &Box) -> GameResult { Ok(TextureId::from(0)) } fn prepare_imgui(&mut self, _ui: &Ui) -> GameResult { Ok(()) } fn render_imgui(&mut self, _draw_data: &DrawData) -> GameResult { Ok(()) } fn supports_vertex_draw(&self) -> bool { true } fn draw_triangle_list( &mut self, vertices: &[VertexData], texture: Option<&Box>, shader: BackendShader, ) -> GameResult<()> { let cmdbuf = &self.cmdbuf[self.slot as usize]; self.vbo.transfer(vertices, &self.device); cmdbuf.bind_vtx_buffer(0, self.vbo.buffer.get_gpu_addr(), self.vbo.buffer.get_size()); match shader { BackendShader::Fill | BackendShader::WaterFill(_, _, _) => { self.color_shader.update_uniforms(VertUBO { proj_mtx: self.curr_mtx }); self.color_shader.bind(cmdbuf); } BackendShader::Texture => { self.texture_shader.update_uniforms(VertUBO { proj_mtx: self.curr_mtx }); self.texture_shader.bind(cmdbuf); } } cmdbuf.draw(Primitive::Triangles, vertices.len() as u32, 1, 0, 0); cmdbuf.barrier(Barrier::Fragments, InvalidateFlags::None); self.queue.submit_commands(cmdbuf.finish_list()); self.queue.wait_idle(); cmdbuf.clear(); Ok(()) } } pub fn web_open(url: &str) -> std::io::Result<()> { use std::io::{Error, ErrorKind}; let mut config = nx::WebCommonConfig::new(); unsafe { let curl = std::ffi::CString::new(url).unwrap(); let ret = nx::webPageCreate(&mut config, curl.as_ptr()); if ret != 0 { return Err(Error::new(ErrorKind::Other, "webPageCreate failed")); } let whitelist = std::ffi::CString::new("^http*").unwrap(); let ret = nx::webConfigSetWhitelist(&mut config, whitelist.as_ptr()); if ret != 0 { return Err(Error::new(ErrorKind::Other, "webConfigSetWhitelist failed")); } let ret = nx::webConfigShow(&mut config, std::ptr::null_mut()); if ret != 0 { return Err(Error::new(ErrorKind::Other, "webConfigShow failed")); } } Ok(()) } pub fn mount_romfs() -> bool { // let title_ids = [ // (0x01000D9007C28000u64, "Cave Story+ (Japan)"), // (0x0100B7D0022EE000u64, "Cave Story+ (US)"), // (0x0100A55003B5C000u64, "Cave Story+ (EU)"), // ]; // // romfsMountDataStorageFromProgram let romfs_partition = std::ffi::CString::new("romfs").unwrap(); // // for &(tid, name) in title_ids.iter() { // log::info!("Trying to mount RomFS for {} [{:016X}]", name, tid); // let ret = unsafe { nx::romfsMountDataStorageFromProgram(tid, romfs_partition.as_ptr()) }; // log::info!("romfsMountDataStorageFromProgram returned {:#04x}", ret); // if ret == 0 { // log::info!("RomFS mounted for {} [{:016X}]", name, tid); // return true; // } // } let ret = unsafe { nx::romfsMountFromCurrentProcess(romfs_partition.as_ptr()) }; log::info!("romfsMountFromCurrentProcess returned {:#04x}", ret); if ret == 0 { return true; } false }