doukutsu-rs/src/framework/backend_horizon.rs

1379 lines
48 KiB
Rust

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<Box<dyn Backend>> {
Ok(Box::new(HorizonBackend))
}
}
impl Backend for HorizonBackend {
fn create_event_loop(&self, _ctx: &Context) -> GameResult<Box<dyn BackendEventLoop>> {
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::North,
Button::West,
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 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(0.0, 1.0));
ctx.gamepad_context.set_axis_value(id as u32, Axis::LeftY, analog_y.clamp(0.0, 1.0));
ctx.gamepad_context.set_axis_value(id as u32, Axis::RightX, (-analog_x).clamp(0.0, 1.0));
ctx.gamepad_context.set_axis_value(id as u32, Axis::RightY, (-analog_y).clamp(0.0, 1.0));
}
}
}
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<Box<dyn BackendRenderer>> {
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<dyn BackendGamepad> {
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::<VertexData, _, _>(|v| &v.position) as u16)
.set_size(VtxAttribSize::_2x32)
.set_type(VtxAttribType::Float),
*VtxAttribState::new()
.set_offset(field_offset::<VertexData, _, _>(|v| &v.uv) as u16)
.set_size(VtxAttribSize::_2x32)
.set_type(VtxAttribType::Float),
*VtxAttribState::new()
.set_offset(field_offset::<VertexData, _, _>(|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::<VertexData>() 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<Self> {
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::<VertexData>();
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> {
ubo_mem_block: deko3d::MemBlock,
code_mem_block: deko3d::MemBlock,
vtx_shader: deko3d::Shader,
frag_shader: deko3d::Shader,
data: UBO,
}
impl<UBO: Default> Deko3DShader<UBO> {
pub fn new(
device: &deko3d::Device,
vertex_shader_binary: &[u8],
fragment_shader_binary: &[u8],
) -> GameResult<Self> {
let ubo_size = mem::size_of::<UBO>();
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::<UBO>() 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<VertexData>,
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::<Deko3DTextureDesc, _, _>(|d| &d.image);
let sampler_offset = field_offset::<Deko3DTextureDesc, _, _>(|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<VertUBO>,
color_shader: Deko3DShader<VertUBO>,
curr_mtx: [[f32; 4]; 4],
width: u32,
height: u32,
fb_width: u32,
fb_height: u32,
slot: i32,
imgui: UnsafeCell<imgui::Context>,
}
impl Deko3DRenderer {
fn new(device: deko3d::Device, imgui: imgui::Context) -> GameResult<Box<dyn BackendRenderer>> {
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::<VertUBO>::new(
&device,
include_bytes!("shaders/deko3d/vertex_basic.dksh"),
include_bytes!("shaders/deko3d/fragment_textured.dksh"),
)?;
let color_shader = Deko3DShader::<VertUBO>::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<Box<dyn BackendTexture>> {
let img_total = width as u32 * height as u32 * 4;
let desc_memory = MemBlockMaker::new(
&self.device,
align(std::mem::size_of::<Deko3DTextureDesc>() 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<Box<dyn BackendTexture>> {
let img_total = width as u32 * height as u32 * 4;
let desc_memory = MemBlockMaker::new(
&self.device,
align(std::mem::size_of::<Deko3DTextureDesc>() 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::<Deko3DTextureDesc, _, _>(|d| &d.image);
let sampler_offset = field_offset::<Deko3DTextureDesc, _, _>(|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<dyn BackendTexture>>) -> GameResult {
if let Some(texture) = texture {
let deko_texture = texture
.as_any()
.downcast_ref::<Deko3DTexture>()
.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<isize>, 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<isize>, _line_width: usize, _color: Color) -> GameResult {
Ok(())
}
fn set_clip_rect(&mut self, rect: Option<Rect>) -> 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<dyn BackendTexture>) -> GameResult<TextureId> {
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<dyn BackendTexture>>,
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
}