1400 lines
48 KiB
Rust
1400 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] }))
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
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<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)
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
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(())
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|