diff options
33 files changed, 724 insertions, 316 deletions
diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp index 81231e1e5..997e3bc7d 100644 --- a/src/citra/emu_window/emu_window_glfw.cpp +++ b/src/citra/emu_window/emu_window_glfw.cpp @@ -16,18 +16,34 @@ EmuWindow_GLFW* EmuWindow_GLFW::GetEmuWindow(GLFWwindow* win) { return static_cast<EmuWindow_GLFW*>(glfwGetWindowUserPointer(win)); } +void EmuWindow_GLFW::OnMouseButtonEvent(GLFWwindow* win, int button, int action, int mods) { + if (button == GLFW_MOUSE_BUTTON_LEFT) { + auto emu_window = GetEmuWindow(win); + auto layout = emu_window->GetFramebufferLayout(); + double x, y; + glfwGetCursorPos(win, &x, &y); + + if (action == GLFW_PRESS) + emu_window->TouchPressed(static_cast<unsigned>(x), static_cast<unsigned>(y)); + else if (action == GLFW_RELEASE) + emu_window->TouchReleased(); + } +} + +void EmuWindow_GLFW::OnCursorPosEvent(GLFWwindow* win, double x, double y) { + GetEmuWindow(win)->TouchMoved(static_cast<unsigned>(x), static_cast<unsigned>(y)); +} + /// Called by GLFW when a key event occurs void EmuWindow_GLFW::OnKeyEvent(GLFWwindow* win, int key, int scancode, int action, int mods) { - - int keyboard_id = GetEmuWindow(win)->keyboard_id; + auto emu_window = GetEmuWindow(win); + int keyboard_id = emu_window->keyboard_id; if (action == GLFW_PRESS) { - EmuWindow::KeyPressed({key, keyboard_id}); + emu_window->KeyPressed({key, keyboard_id}); } else if (action == GLFW_RELEASE) { - EmuWindow::KeyReleased({key, keyboard_id}); + emu_window->KeyReleased({key, keyboard_id}); } - - Service::HID::PadUpdateComplete(); } /// Whether the window is still open, and a close request hasn't yet been sent @@ -88,6 +104,8 @@ EmuWindow_GLFW::EmuWindow_GLFW() { // Setup callbacks glfwSetKeyCallback(m_render_window, OnKeyEvent); + glfwSetMouseButtonCallback(m_render_window, OnMouseButtonEvent); + glfwSetCursorPosCallback(m_render_window, OnCursorPosEvent); glfwSetFramebufferSizeCallback(m_render_window, OnFramebufferResizeEvent); glfwSetWindowSizeCallback(m_render_window, OnClientAreaResizeEvent); diff --git a/src/citra/emu_window/emu_window_glfw.h b/src/citra/emu_window/emu_window_glfw.h index 5252fccc8..16c109b79 100644 --- a/src/citra/emu_window/emu_window_glfw.h +++ b/src/citra/emu_window/emu_window_glfw.h @@ -27,6 +27,10 @@ public: static void OnKeyEvent(GLFWwindow* win, int key, int scancode, int action, int mods); + static void OnMouseButtonEvent(GLFWwindow* window, int button, int action, int mods); + + static void OnCursorPosEvent(GLFWwindow* window, double x, double y); + /// Whether the window is still open, and a close request hasn't yet been sent const bool IsOpen(); diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index a040e75c1..b81bd6167 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -268,14 +268,33 @@ QByteArray GRenderWindow::saveGeometry() void GRenderWindow::keyPressEvent(QKeyEvent* event) { - EmuWindow::KeyPressed({event->key(), keyboard_id}); - Service::HID::PadUpdateComplete(); + this->KeyPressed({event->key(), keyboard_id}); } void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { - EmuWindow::KeyReleased({event->key(), keyboard_id}); - Service::HID::PadUpdateComplete(); + this->KeyReleased({event->key(), keyboard_id}); +} + +void GRenderWindow::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + auto pos = event->pos(); + this->TouchPressed(static_cast<unsigned>(pos.x()), static_cast<unsigned>(pos.y())); + } +} + +void GRenderWindow::mouseMoveEvent(QMouseEvent *event) +{ + auto pos = event->pos(); + this->TouchMoved(static_cast<unsigned>(pos.x()), static_cast<unsigned>(pos.y())); +} + +void GRenderWindow::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + this->TouchReleased(); } void GRenderWindow::ReloadSetKeymaps() diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h index a55db682a..288da45a1 100644 --- a/src/citra_qt/bootmanager.h +++ b/src/citra_qt/bootmanager.h @@ -121,6 +121,10 @@ public: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void ReloadSetKeymaps() override; void OnClientAreaResized(unsigned width, unsigned height); diff --git a/src/common/common_paths.h b/src/common/common_paths.h index eb43d589f..440b06060 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -17,13 +17,12 @@ // The user data dir #define ROOT_DIR "." -#ifdef _WIN32 - #define USERDATA_DIR "user" - #define EMU_DATA_DIR "Citra Emulator" +#define USERDATA_DIR "user" +#ifdef USER_DIR + #define EMU_DATA_DIR USER_DIR #else - #define USERDATA_DIR "user" - #ifdef USER_DIR - #define EMU_DATA_DIR USER_DIR + #ifdef _WIN32 + #define EMU_DATA_DIR "Citra Emulator" #else #define EMU_DATA_DIR "citra-emu" #endif diff --git a/src/common/emu_window.cpp b/src/common/emu_window.cpp index 6459d2f32..6516fc633 100644 --- a/src/common/emu_window.cpp +++ b/src/common/emu_window.cpp @@ -6,18 +6,61 @@ #include "video_core/video_core.h" void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) { - Service::HID::PadState mapped_key = KeyMap::GetPadKey(key); - - Service::HID::PadButtonPress(mapped_key); + pad_state.hex |= KeyMap::GetPadKey(key).hex; } void EmuWindow::KeyReleased(KeyMap::HostDeviceKey key) { - Service::HID::PadState mapped_key = KeyMap::GetPadKey(key); + pad_state.hex &= ~KeyMap::GetPadKey(key).hex; +} + +/** + * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout + * @param layout FramebufferLayout object describing the framebuffer size and screen positions + * @param framebuffer_x Framebuffer x-coordinate to check + * @param framebuffer_y Framebuffer y-coordinate to check + * @return True if the coordinates are within the touchpad, otherwise false + */ +static bool IsWithinTouchscreen(const EmuWindow::FramebufferLayout& layout, unsigned framebuffer_x, + unsigned framebuffer_y) { + return (framebuffer_y >= layout.bottom_screen.top && + framebuffer_y < layout.bottom_screen.bottom && + framebuffer_x >= layout.bottom_screen.left && + framebuffer_x < layout.bottom_screen.right); +} + +void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { + if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) + return; + + touch_x = VideoCore::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) / + (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); + touch_y = VideoCore::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) / + (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); - Service::HID::PadButtonRelease(mapped_key); + touch_pressed = true; + pad_state.touch = 1; } -EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, unsigned height) { +void EmuWindow::TouchReleased() { + touch_pressed = false; + touch_x = 0; + touch_y = 0; + pad_state.touch = 0; +} + +void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { + if (!touch_pressed) + return; + + if (IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) + TouchPressed(framebuffer_x, framebuffer_y); + else + TouchReleased(); +} + +EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, + unsigned height) { + ASSERT(width > 0); ASSERT(height > 0); diff --git a/src/common/emu_window.h b/src/common/emu_window.h index f6099fdb6..c8e2de04a 100644 --- a/src/common/emu_window.h +++ b/src/common/emu_window.h @@ -71,10 +71,48 @@ public: virtual void ReloadSetKeymaps() = 0; /// Signals a key press action to the HID module - static void KeyPressed(KeyMap::HostDeviceKey key); + void KeyPressed(KeyMap::HostDeviceKey key); /// Signals a key release action to the HID module - static void KeyReleased(KeyMap::HostDeviceKey key); + void KeyReleased(KeyMap::HostDeviceKey key); + + /** + * Signal that a touch pressed event has occurred (e.g. mouse click pressed) + * @param framebuffer_x Framebuffer x-coordinate that was pressed + * @param framebuffer_y Framebuffer y-coordinate that was pressed + */ + void TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y); + + /// Signal that a touch released event has occurred (e.g. mouse click released) + void TouchReleased(); + + /** + * Signal that a touch movement event has occurred (e.g. mouse was moved over the emu window) + * @param framebuffer_x Framebuffer x-coordinate + * @param framebuffer_y Framebuffer y-coordinate + */ + void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); + + /** + * Gets the current pad state (which buttons are pressed and the circle pad direction). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Fix this function to be thread-safe. + * @return PadState object indicating the current pad state + */ + const Service::HID::PadState GetPadState() const { + return pad_state; + } + + /** + * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed). + * @note This should be called by the core emu thread to get a state set by the window thread. + * @todo Fix this function to be thread-safe. + * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and + * `pressed` is true if the touch screen is currently being pressed + */ + const std::tuple<u16, u16, bool> GetTouchState() const { + return std::make_tuple(touch_x, touch_y, touch_pressed); + } /** * Returns currently active configuration. @@ -100,21 +138,15 @@ public: return framebuffer_layout; } - /** - * Gets window client area width in logical coordinates. - * @note For high-DPI systems, this is smaller than the framebuffer size. - * @note This method is thread-safe - */ - std::pair<unsigned,unsigned> GetClientAreaSize() const { - return std::make_pair(client_area_width, client_area_height); - } - protected: - EmuWindow() - { + EmuWindow() { // TODO: Find a better place to set this. config.min_client_area_size = std::make_pair(400u, 480u); active_config = config; + pad_state.hex = 0; + touch_x = 0; + touch_y = 0; + touch_pressed = false; } virtual ~EmuWindow() {} @@ -168,4 +200,11 @@ private: WindowConfig config; ///< Internal configuration (changes pending for being applied in ProcessConfigurationChanges) WindowConfig active_config; ///< Internal active configuration + + bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false + + u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) + u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) + + Service::HID::PadState pad_state; }; diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 7b479b569..649640e72 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -45,6 +45,7 @@ static std::shared_ptr<Logger> global_logger; SUB(Service, SOC) \ CLS(HW) \ SUB(HW, Memory) \ + SUB(HW, LCD) \ SUB(HW, GPU) \ CLS(Frontend) \ CLS(Render) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 7b67b3c07..83d64145b 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -65,6 +65,7 @@ enum class Class : ClassType { Service_SOC, ///< The SOC (Socket) service HW, ///< Low-level hardware emulation HW_Memory, ///< Memory-map and address translation + HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration diff --git a/src/common/platform.h b/src/common/platform.h index ba1109c9f..e27d6e31f 100644 --- a/src/common/platform.h +++ b/src/common/platform.h @@ -83,7 +83,7 @@ inline struct tm* localtime_r(const time_t *clock, struct tm *result) { } #endif -#else +#else // EMU_PLATFORM != PLATFORM_WINDOWS #define EMU_FASTCALL __attribute__((fastcall)) #define __stdcall @@ -92,10 +92,6 @@ inline struct tm* localtime_r(const time_t *clock, struct tm *result) { #define BOOL bool #define DWORD u32 -#endif - -#if EMU_PLATFORM != PLATFORM_WINDOWS - // TODO: Hacks.. #include <limits.h> diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 212da25c5..33e5be3a4 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -87,6 +87,7 @@ set(SRCS hle/svc.cpp hw/gpu.cpp hw/hw.cpp + hw/lcd.cpp loader/elf.cpp loader/loader.cpp loader/ncch.cpp @@ -196,6 +197,7 @@ set(HEADERS hle/svc.h hw/gpu.h hw/hw.h + hw/lcd.h loader/elf.h loader/loader.h loader/ncch.h diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index ef37ee055..fe1e584ad 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -74,12 +74,6 @@ public: virtual void SetCPSR(u32 cpsr) = 0; /** - * Returns the number of clock ticks since the last rese - * @return Returns number of clock ticks - */ - virtual u64 GetTicks() const = 0; - - /** * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time) * @param ticks Number of ticks to advance the CPU core */ diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index bbcbbdd2b..cb1a410a0 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp @@ -68,11 +68,6 @@ void ARM_DynCom::SetCPSR(u32 cpsr) { state->Cpsr = cpsr; } -u64 ARM_DynCom::GetTicks() const { - // TODO(Subv): Remove ARM_DynCom::GetTicks() and use CoreTiming::GetTicks() directly once ARMemu is gone - return CoreTiming::GetTicks(); -} - void ARM_DynCom::AddTicks(u64 ticks) { down_count -= ticks; if (down_count < 0) diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index 213cac1ad..a7f95d307 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h @@ -23,7 +23,6 @@ public: u32 GetCPSR() const override; void SetCPSR(u32 cpsr) override; - u64 GetTicks() const override; void AddTicks(u64 ticks) override; void ResetContext(Core::ThreadContext& context, u32 stack_top, u32 entry_point, u32 arg); diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 2f72f5077..d953adba9 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -4488,10 +4488,6 @@ unsigned InterpreterMainLoop(ARMul_State* state) { unsigned int operand2 = ROTATE_RIGHT_32(RM, 8 * inst_cream->rotate) & 0xffff; RD = RN + operand2; - if (inst_cream->Rn == 15 || inst_cream->Rm == 15) { - LOG_ERROR(Core_ARM11, "invalid operands for UXTAH"); - CITRA_IGNORE_EXIT(-1); - } } cpu->Reg[15] += GET_INST_SIZE(cpu); INC_PC(sizeof(uxtah_inst)); @@ -4822,10 +4818,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { uint64_t rm = RM; uint64_t rs = RS; uint64_t rn = RN; - if (inst_cream->Rm == 15 || inst_cream->Rs == 15 || inst_cream->Rn == 15) { - LOG_ERROR(Core_ARM11, "invalid operands for MLA"); - CITRA_IGNORE_EXIT(-1); - } + RD = static_cast<uint32_t>((rm * rs + rn) & 0xffffffff); if (inst_cream->S) { UPDATE_NFLAG(RD); @@ -5104,10 +5097,10 @@ unsigned InterpreterMainLoop(ARMul_State* state) { PLD_INST: { - // Instruction not implemented - //LOG_CRITICAL(Core_ARM11, "unimplemented instruction"); + // Not implemented. PLD is a hint instruction, so it's optional. + cpu->Reg[15] += GET_INST_SIZE(cpu); - INC_PC(sizeof(stc_inst)); + INC_PC(sizeof(pld_inst)); FETCH_INST; GOTO_NEXT_INST; } @@ -6033,15 +6026,12 @@ unsigned InterpreterMainLoop(ARMul_State* state) { if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { sxtb_inst* inst_cream = (sxtb_inst*)inst_base->component; - if (inst_cream->Rm == 15) { - LOG_ERROR(Core_ARM11, "invalid operand for SXTB"); - CITRA_IGNORE_EXIT(-1); - } unsigned int operand2 = ROTATE_RIGHT_32(RM, 8 * inst_cream->rotate); if (BIT(operand2, 7)) { operand2 |= 0xffffff00; - } else + } else { operand2 &= 0xff; + } RD = operand2; } cpu->Reg[15] += GET_INST_SIZE(cpu); @@ -6299,8 +6289,7 @@ unsigned InterpreterMainLoop(ARMul_State* state) { swp_inst* inst_cream = (swp_inst*)inst_base->component; addr = RN; - unsigned int value; - value = Memory::Read32(addr); + unsigned int value = Memory::Read32(addr); Memory::Write32(addr, RM); RD = value; @@ -6329,10 +6318,6 @@ unsigned InterpreterMainLoop(ARMul_State* state) { if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { sxtab_inst* inst_cream = (sxtab_inst*)inst_base->component; - // R15 should be check - if(inst_cream->Rn == 15 || inst_cream->Rm == 15 || inst_cream->Rd ==15){ - CITRA_IGNORE_EXIT(-1); - } unsigned int operand2 = ROTATE_RIGHT_32(RM, 8 * inst_cream->rotate) & 0xff; // Sign extend for byte @@ -6383,10 +6368,6 @@ unsigned InterpreterMainLoop(ARMul_State* state) { if (inst_base->cond == 0xE || CondPassed(cpu, inst_base->cond)) { sxtah_inst* inst_cream = (sxtah_inst*)inst_base->component; - // R15 should be check - if(inst_cream->Rn == 15 || inst_cream->Rm == 15 || inst_cream->Rd ==15) { - CITRA_IGNORE_EXIT(-1); - } unsigned int operand2 = ROTATE_RIGHT_32(RM, 8 * inst_cream->rotate) & 0xffff; // Sign extend for half operand2 = (0x8000 & operand2) ? (0xFFFF0000 | operand2) : operand2; diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index c23cfa3c8..cff585698 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -7,14 +7,20 @@ #include "core/mem_map.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/result.h" #include "gsp_gpu.h" +#include "core/hw/hw.h" #include "core/hw/gpu.h" +#include "core/hw/lcd.h" #include "video_core/gpu_debugger.h" // Main graphics debugger object - TODO: Here is probably not the best place for this GraphicsDebugger g_debugger; +// Beginning address of HW regs +const static u32 REGS_BEGIN = 0x1EB00000; + //////////////////////////////////////////////////////////////////////////////////////////////////// // Namespace GSP_GPU @@ -85,7 +91,7 @@ static void WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) { return; while (size_in_bytes > 0) { - GPU::Write<u32>(base_address + 0x1EB00000, *data); + HW::Write<u32>(base_address + REGS_BEGIN, *data); size_in_bytes -= 4; ++data; @@ -128,15 +134,15 @@ static void WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* return; while (size_in_bytes > 0) { - const u32 reg_address = base_address + 0x1EB00000; + const u32 reg_address = base_address + REGS_BEGIN; u32 reg_value; - GPU::Read<u32>(reg_value, reg_address); + HW::Read<u32>(reg_value, reg_address); // Update the current value of the register only for set mask bits reg_value = (reg_value & ~*masks) | (*data | *masks); - GPU::Write<u32>(reg_address, reg_value); + HW::Write<u32>(reg_address, reg_value); size_in_bytes -= 4; ++data; @@ -188,7 +194,7 @@ static void ReadHWRegs(Service::Interface* self) { u32* dst = (u32*)Memory::GetPointer(cmd_buff[0x41]); while (size > 0) { - GPU::Read<u32>(*dst, reg_addr + 0x1EB00000); + HW::Read<u32>(*dst, reg_addr + REGS_BEGIN); size -= 4; ++dst; @@ -427,6 +433,32 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { } } +/** + * GSP_GPU::SetLcdForceBlack service function + * + * Enable or disable REG_LCDCOLORFILL with the color black. + * + * Inputs: + * 1: Black color fill flag (0 = don't fill, !0 = fill) + * Outputs: + * 1: Result code + */ +static void SetLcdForceBlack(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + bool enable_black = cmd_buff[1] != 0; + LCD::Regs::ColorFill data = {0}; + + // Since data is already zeroed, there is no need to explicitly set + // the color to black (all zero). + data.is_enabled = enable_black; + + LCD::Write(HW::VADDR_LCD + 4 * LCD_REG_INDEX(color_fill_top), data.raw); // Top LCD + LCD::Write(HW::VADDR_LCD + 4 * LCD_REG_INDEX(color_fill_bottom), data.raw); // Bottom LCD + + cmd_buff[1] = RESULT_SUCCESS.raw; +} + /// This triggers handling of the GX command written to the command buffer in shared memory. static void TriggerCmdReqQueue(Service::Interface* self) { // Iterate through each thread's command queue... @@ -460,7 +492,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00080082, FlushDataCache, "FlushDataCache"}, {0x00090082, nullptr, "InvalidateDataCache"}, {0x000A0044, nullptr, "RegisterInterruptEvents"}, - {0x000B0040, nullptr, "SetLcdForceBlack"}, + {0x000B0040, SetLcdForceBlack, "SetLcdForceBlack"}, {0x000C0000, TriggerCmdReqQueue, "TriggerCmdReqQueue"}, {0x000D0140, nullptr, "SetDisplayTransfer"}, {0x000E0180, nullptr, "SetTextureCopy"}, diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index e0689be2e..138603d9b 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -7,36 +7,30 @@ #include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_user.h" -#include "core/arm/arm_interface.h" +#include "core/core_timing.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/hle.h" +#include "video_core/video_core.h" + namespace Service { namespace HID { -Kernel::SharedPtr<Kernel::SharedMemory> g_shared_mem = nullptr; - -Kernel::SharedPtr<Kernel::Event> g_event_pad_or_touch_1; -Kernel::SharedPtr<Kernel::Event> g_event_pad_or_touch_2; -Kernel::SharedPtr<Kernel::Event> g_event_accelerometer; -Kernel::SharedPtr<Kernel::Event> g_event_gyroscope; -Kernel::SharedPtr<Kernel::Event> g_event_debug_pad; - -// Next Pad state update information -static PadState next_state = {{0}}; -static u32 next_index = 0; -static s16 next_circle_x = 0; -static s16 next_circle_y = 0; - -/** - * Gets a pointer to the PadData structure inside HID shared memory - */ -static inline PadData* GetPadData() { - if (g_shared_mem == nullptr) - return nullptr; - return reinterpret_cast<PadData*>(g_shared_mem->GetPointer().ValueOr(nullptr)); -} +static const int MAX_CIRCLEPAD_POS = 0x9C; ///< Max value for a circle pad position + +// Handle to shared memory region designated to HID_User service +static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem = nullptr; + +// Event handles +static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1 = nullptr; +static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2 = nullptr; +static Kernel::SharedPtr<Kernel::Event> event_accelerometer = nullptr; +static Kernel::SharedPtr<Kernel::Event> event_gyroscope = nullptr; +static Kernel::SharedPtr<Kernel::Event> event_debug_pad = nullptr; + +static u32 next_pad_index = 0; +static u32 next_touch_index = 0; // TODO(peachum): // Add a method for setting analog input from joystick device for the circle Pad. @@ -51,103 +45,114 @@ static inline PadData* GetPadData() { // * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41 // * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41 -/** - * Circle Pad from keys. - * - * This is implemented as "pushed all the way to an edge (max) or centered (0)". - * - * Indicate the circle pad is pushed completely to the edge in 1 of 8 directions. - */ -static void UpdateNextCirclePadState() { - static const s16 max_value = 0x9C; - next_circle_x = next_state.circle_left ? -max_value : 0x0; - next_circle_x += next_state.circle_right ? max_value : 0x0; - next_circle_y = next_state.circle_down ? -max_value : 0x0; - next_circle_y += next_state.circle_up ? max_value : 0x0; -} - -/** - * Sets a Pad state (button or button combo) as pressed - */ -void PadButtonPress(const PadState& pad_state) { - next_state.hex |= pad_state.hex; - UpdateNextCirclePadState(); -} - -/** - * Sets a Pad state (button or button combo) as released - */ -void PadButtonRelease(const PadState& pad_state) { - next_state.hex &= ~pad_state.hex; - UpdateNextCirclePadState(); -} - -/** - * Called after all Pad changes to be included in this update have been made, - * including both Pad key changes and analog circle Pad changes. - */ -void PadUpdateComplete() { - PadData* pad_data = GetPadData(); +void HIDUpdate() { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer().ValueOr(nullptr)); + const PadState state = VideoCore::g_emu_window->GetPadState(); - if (pad_data == nullptr) { + if (mem == nullptr) { + LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!"); return; } - // Update PadData struct - pad_data->current_state.hex = next_state.hex; - pad_data->index = next_index; - next_index = (next_index + 1) % pad_data->entries.size(); + mem->pad.current_state.hex = state.hex; + mem->pad.index = next_pad_index; + ++next_touch_index %= mem->pad.entries.size(); // Get the previous Pad state - u32 last_entry_index = (pad_data->index - 1) % pad_data->entries.size(); - PadState old_state = pad_data->entries[last_entry_index].current_state; + u32 last_entry_index = (mem->pad.index - 1) % mem->pad.entries.size(); + PadState old_state = mem->pad.entries[last_entry_index].current_state; // Compute bitmask with 1s for bits different from the old state - PadState changed; - changed.hex = (next_state.hex ^ old_state.hex); - - // Compute what was added - PadState additions; - additions.hex = changed.hex & next_state.hex; - - // Compute what was removed - PadState removals; - removals.hex = changed.hex & old_state.hex; + PadState changed = { { (state.hex ^ old_state.hex) } }; // Get the current Pad entry - PadDataEntry* current_pad_entry = &pad_data->entries[pad_data->index]; + PadDataEntry* pad_entry = &mem->pad.entries[mem->pad.index]; // Update entry properties - current_pad_entry->current_state.hex = next_state.hex; - current_pad_entry->delta_additions.hex = additions.hex; - current_pad_entry->delta_removals.hex = removals.hex; + pad_entry->current_state.hex = state.hex; + pad_entry->delta_additions.hex = changed.hex & state.hex; + pad_entry->delta_removals.hex = changed.hex & old_state.hex;; // Set circle Pad - current_pad_entry->circle_pad_x = next_circle_x; - current_pad_entry->circle_pad_y = next_circle_y; + pad_entry->circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS : + state.circle_right ? MAX_CIRCLEPAD_POS : 0x0; + pad_entry->circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS : + state.circle_up ? MAX_CIRCLEPAD_POS : 0x0; // If we just updated index 0, provide a new timestamp - if (pad_data->index == 0) { - pad_data->index_reset_ticks_previous = pad_data->index_reset_ticks; - pad_data->index_reset_ticks = (s64)Core::g_app_core->GetTicks(); + if (mem->pad.index == 0) { + mem->pad.index_reset_ticks_previous = mem->pad.index_reset_ticks; + mem->pad.index_reset_ticks = (s64)CoreTiming::GetTicks(); + } + + mem->touch.index = next_touch_index; + ++next_touch_index %= mem->touch.entries.size(); + + // Get the current touch entry + TouchDataEntry* touch_entry = &mem->touch.entries[mem->touch.index]; + bool pressed = false; + + std::tie(touch_entry->x, touch_entry->y, pressed) = VideoCore::g_emu_window->GetTouchState(); + touch_entry->valid = pressed ? 1 : 0; + + // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which + // supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being + // converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8). + + // If we just updated index 0, provide a new timestamp + if (mem->touch.index == 0) { + mem->touch.index_reset_ticks_previous = mem->touch.index_reset_ticks; + mem->touch.index_reset_ticks = (s64)CoreTiming::GetTicks(); } // Signal both handles when there's an update to Pad or touch - g_event_pad_or_touch_1->Signal(); - g_event_pad_or_touch_2->Signal(); + event_pad_or_touch_1->Signal(); + event_pad_or_touch_2->Signal(); } void GetIPCHandles(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = 0; // No error + cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) - cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::g_shared_mem).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_1).MoveFrom(); - cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::g_event_pad_or_touch_2).MoveFrom(); - cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::g_event_accelerometer).MoveFrom(); - cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::g_event_gyroscope).MoveFrom(); - cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::g_event_debug_pad).MoveFrom(); + cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom(); + cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom(); + cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom(); + cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom(); + cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom(); + cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom(); +} + +void EnableAccelerometer(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + event_accelerometer->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_HID, "(STUBBED) called"); +} + +void EnableGyroscopeLow(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + event_gyroscope->Signal(); + + cmd_buff[1] = RESULT_SUCCESS.raw; + + LOG_WARNING(Service_HID, "(STUBBED) called"); +} + +void GetSoundVolume(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + + const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume + + cmd_buff[1] = RESULT_SUCCESS.raw; + cmd_buff[2] = volume; + + LOG_WARNING(Service_HID, "(STUBBED) called"); } void HIDInit() { @@ -156,19 +161,22 @@ void HIDInit() { AddService(new HID_U_Interface); AddService(new HID_SPVR_Interface); - g_shared_mem = SharedMemory::Create("HID:SharedMem"); + shared_mem = SharedMemory::Create("HID:SharedMem"); + + next_pad_index = 0; + next_touch_index = 0; // Create event handles - g_event_pad_or_touch_1 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch1"); - g_event_pad_or_touch_2 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch2"); - g_event_accelerometer = Event::Create(RESETTYPE_ONESHOT, "HID:EventAccelerometer"); - g_event_gyroscope = Event::Create(RESETTYPE_ONESHOT, "HID:EventGyroscope"); - g_event_debug_pad = Event::Create(RESETTYPE_ONESHOT, "HID:EventDebugPad"); + event_pad_or_touch_1 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch1"); + event_pad_or_touch_2 = Event::Create(RESETTYPE_ONESHOT, "HID:EventPadOrTouch2"); + event_accelerometer = Event::Create(RESETTYPE_ONESHOT, "HID:EventAccelerometer"); + event_gyroscope = Event::Create(RESETTYPE_ONESHOT, "HID:EventGyroscope"); + event_debug_pad = Event::Create(RESETTYPE_ONESHOT, "HID:EventDebugPad"); } void HIDShutdown() { - } -} -} +} // namespace HID + +} // namespace Service diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 9c6e86f77..97462c7f8 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -18,16 +18,6 @@ namespace Kernel { namespace Service { namespace HID { -// Handle to shared memory region designated to HID_User service -extern Kernel::SharedPtr<Kernel::SharedMemory> g_shared_mem; - -// Event handles -extern Kernel::SharedPtr<Kernel::Event> g_event_pad_or_touch_1; -extern Kernel::SharedPtr<Kernel::Event> g_event_pad_or_touch_2; -extern Kernel::SharedPtr<Kernel::Event> g_event_accelerometer; -extern Kernel::SharedPtr<Kernel::Event> g_event_gyroscope; -extern Kernel::SharedPtr<Kernel::Event> g_event_debug_pad; - /** * Structure of a Pad controller state. */ @@ -65,7 +55,7 @@ struct PadState { }; /** - * Structure of a single entry in the PadData's Pad state history array. + * Structure of a single entry of Pad state history within HID shared memory */ struct PadDataEntry { PadState current_state; @@ -77,24 +67,65 @@ struct PadDataEntry { }; /** - * Structure of all data related to the 3DS Pad. + * Structure of a single entry of touch state history within HID shared memory */ -struct PadData { - s64 index_reset_ticks; - s64 index_reset_ticks_previous; - u32 index; // the index of the last updated Pad state history element +struct TouchDataEntry { + u16 x; ///< Y-coordinate of a touchpad press on the lower screen + u16 y; ///< X-coordinate of a touchpad press on the lower screen + BitField<0, 7, u32> valid; ///< Set to 1 when this entry contains actual X/Y data, otherwise 0 +}; + +/** + * Structure of data stored in HID shared memory + */ +struct SharedMem { + /// Pad data, this is used for buttons and the circle pad + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated pad state entry + + INSERT_PADDING_WORDS(0x2); + + PadState current_state; ///< Current state of the pad buttons + + // TODO(bunnei): Implement `raw_circle_pad_data` field + u32 raw_circle_pad_data; ///< Raw (analog) circle pad data, before being converted - u32 pad1; - u32 pad2; + INSERT_PADDING_WORDS(0x1); - PadState current_state; // same as entries[index].current_state - u32 raw_circle_pad_data; + std::array<PadDataEntry, 8> entries; ///< Last 8 pad entries + } pad; - u32 pad3; + /// Touchpad data, this is used for touchpad input + struct { + s64 index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + s64 index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32 index; ///< Index of the last updated touch entry - std::array<PadDataEntry, 8> entries; // Pad state history + INSERT_PADDING_WORDS(0x1); + + // TODO(bunnei): Implement `raw_entry` field + TouchDataEntry raw_entry; ///< Raw (analog) touch data, before being converted + + std::array<TouchDataEntry, 8> entries; ///< Last 8 touch entries, in pixel coordinates + } touch; }; +// TODO: MSVC does not support using offsetof() on non-static data members even though this +// is technically allowed since C++11. This macro should be enabled once MSVC adds +// support for that. +#ifndef _MSC_VER +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(SharedMem, field_name) == position * 4, \ + "Field "#field_name" has invalid position") + +ASSERT_REG_POSITION(pad.index_reset_ticks, 0x0); +ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); + +#undef ASSERT_REG_POSITION +#endif // !defined(_MSC_VER) + // Pre-defined PadStates for single button presses const PadState PAD_NONE = {{0}}; const PadState PAD_A = {{1u << 0}}; @@ -130,7 +161,7 @@ const PadState PAD_CIRCLE_DOWN = {{1u << 31}}; * None * Outputs: * 1 : Result of function, 0 on success, otherwise error code - * 2 : Unused + * 2 : IPC Command Structure translate-header * 3 : Handle to HID_User shared memory * 4 : Event signaled by HID_User * 5 : Event signaled by HID_User @@ -140,12 +171,41 @@ const PadState PAD_CIRCLE_DOWN = {{1u << 31}}; */ void GetIPCHandles(Interface* self); -// Methods for updating the HID module's state -void PadButtonPress(const PadState& pad_state); -void PadButtonRelease(const PadState& pad_state); -void PadUpdateComplete(); +/** + * HID::EnableAccelerometer service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void EnableAccelerometer(Interface* self); + +/** + * HID::EnableGyroscopeLow service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void EnableGyroscopeLow(Interface* self); + +/** + * HID::GetSoundVolume service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8 output value + */ +void GetSoundVolume(Interface* self); +/// Checks for user input updates +void HIDUpdate(); + +/// Initialize HID service void HIDInit(); + +/// Shutdown HID service void HIDShutdown(); } diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp index 790dcabbf..f296b076f 100644 --- a/src/core/hle/service/hid/hid_spvr.cpp +++ b/src/core/hle/service/hid/hid_spvr.cpp @@ -13,13 +13,13 @@ const Interface::FunctionInfo FunctionTable[] = { {0x000A0000, GetIPCHandles, "GetIPCHandles"}, {0x000B0000, nullptr, "StartAnalogStickCalibration"}, {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, nullptr, "EnableAccelerometer"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, {0x00120000, nullptr, "DisableAccelerometer"}, - {0x00130000, nullptr, "EnableGyroscopeLow"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, {0x00140000, nullptr, "DisableGyroscopeLow"}, {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, nullptr, "GetSoundVolume"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_SPVR_Interface::HID_SPVR_Interface() { diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp index 1d0accefe..3682c9416 100644 --- a/src/core/hle/service/hid/hid_user.cpp +++ b/src/core/hle/service/hid/hid_user.cpp @@ -3,8 +3,6 @@ // Refer to the license.txt file included. #include "core/hle/hle.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_user.h" @@ -12,14 +10,14 @@ namespace Service { namespace HID { const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x00110000, nullptr, "EnableAccelerometer"}, - {0x00120000, nullptr, "DisableAccelerometer"}, - {0x00130000, nullptr, "EnableGyroscopeLow"}, - {0x00140000, nullptr, "DisableGyroscopeLow"}, - {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, nullptr, "GetSoundVolume"}, + {0x000A0000, GetIPCHandles, "GetIPCHandles"}, + {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, nullptr, "DisableAccelerometer"}, + {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, nullptr, "DisableGyroscopeLow"}, + {0x00150000, nullptr, "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, nullptr, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, GetSoundVolume, "GetSoundVolume"}, }; HID_U_Interface::HID_U_Interface() { diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 17385f9b2..bbb4eb9cd 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -7,8 +7,9 @@ #include "common/string_util.h" #include "common/symbols.h" -#include "core/arm/arm_interface.h" +#include "core/core_timing.h" #include "core/mem_map.h" +#include "core/arm/arm_interface.h" #include "core/hle/kernel/address_arbiter.h" #include "core/hle/kernel/event.h" @@ -551,7 +552,7 @@ static void SleepThread(s64 nanoseconds) { /// This returns the total CPU ticks elapsed since the CPU was powered-on static s64 GetSystemTick() { - return (s64)Core::g_app_core->GetTicks(); + return (s64)CoreTiming::GetTicks(); } /// Creates a memory block at the specified address with the specified permissions and size diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index ca33557ae..e6983a225 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -14,13 +14,15 @@ #include "core/hle/hle.h" #include "core/hle/service/gsp_gpu.h" #include "core/hle/service/dsp_dsp.h" +#include "core/hle/service/hid/hid.h" +#include "core/hw/hw.h" #include "core/hw/gpu.h" #include "video_core/command_processor.h" #include "video_core/utils.h" #include "video_core/video_core.h" -#include <video_core/color.h> +#include "video_core/color.h" namespace GPU { @@ -40,7 +42,7 @@ static bool last_skip_frame = false; template <typename T> inline void Read(T &var, const u32 raw_addr) { - u32 addr = raw_addr - 0x1EF00000; + u32 addr = raw_addr - HW::VADDR_GPU; u32 index = addr / 4; // Reads other than u32 are untested, so I'd rather have them abort than silently fail @@ -54,7 +56,7 @@ inline void Read(T &var, const u32 raw_addr) { template <typename T> inline void Write(u32 addr, const T data) { - addr -= 0x1EF00000; + addr -= HW::VADDR_GPU; u32 index = addr / 4; // Writes other than u32 are untested, so I'd rather have them abort than silently fail @@ -150,8 +152,17 @@ inline void Write(u32 addr, const T data) { for (u32 x = 0; x < output_width; ++x) { Math::Vec4<u8> src_color = { 0, 0, 0, 0 }; - u32 scaled_x = x * horizontal_scale; - u32 scaled_y = y * vertical_scale; + // Calculate the [x,y] position of the input image + // based on the current output position and the scale + u32 input_x = x * horizontal_scale; + u32 input_y = y * vertical_scale; + + if (config.flip_vertically) { + // Flip the y value of the output data, + // we do this after calculating the [x,y] position of the input image + // to account for the scaling options. + y = output_height - y - 1; + } u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format); u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format); @@ -163,14 +174,14 @@ inline void Write(u32 addr, const T data) { u32 coarse_y = y & ~7; u32 stride = output_width * dst_bytes_per_pixel; - src_offset = (scaled_x + scaled_y * config.input_width) * src_bytes_per_pixel; + src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel; dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride; } else { // Interpret the input as tiled and the output as linear - u32 coarse_y = scaled_y & ~7; + u32 coarse_y = input_y & ~7; u32 stride = config.input_width * src_bytes_per_pixel; - src_offset = VideoCore::GetMortonOffset(scaled_x, scaled_y, src_bytes_per_pixel) + coarse_y * stride; + src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride; dst_offset = (x + y * output_width) * dst_bytes_per_pixel; } @@ -300,6 +311,9 @@ static void VBlankCallback(u64 userdata, int cycles_late) { // this. Certain games expect this to be periodically signaled. DSP_DSP::SignalInterrupt(); + // Check for user input updates + Service::HID::HIDUpdate(); + // Reschedule recurrent event CoreTiming::ScheduleEvent(frame_ticks - cycles_late, vblank_event); } @@ -319,8 +333,6 @@ void Init() { framebuffer_top.address_right2 = 0x182B9800; framebuffer_sub.address_left1 = 0x1848F000; framebuffer_sub.address_left2 = 0x184C7800; - //framebuffer_sub.address_right1 = unknown; - //framebuffer_sub.address_right2 = unknown; framebuffer_top.width = 240; framebuffer_top.height = 400; diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index d07490db0..c8f884494 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -197,7 +197,7 @@ struct Regs { union { u32 flags; - BitField< 0, 1, u32> flip_data; // flips input data horizontally (TODO) if true + BitField< 0, 1, u32> flip_vertically; // flips input data vertically BitField< 1, 1, u32> output_tiled; // Converts from linear to tiled format BitField< 3, 1, u32> raw_copy; // Copies the data without performing any processing BitField< 8, 3, PixelFormat> input_format; @@ -251,6 +251,8 @@ struct Regs { return content[index]; } +#undef ASSERT_MEMBER_SIZE + private: /* * Most physical addresses which GPU registers refer to are 8-byte aligned. diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index a63ba6eeb..bed50af50 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -6,43 +6,19 @@ #include "core/hw/hw.h" #include "core/hw/gpu.h" +#include "core/hw/lcd.h" namespace HW { -enum { - VADDR_HASH = 0x1EC01000, - VADDR_CSND = 0x1EC03000, - VADDR_DSP = 0x1EC40000, - VADDR_PDN = 0x1EC41000, - VADDR_CODEC = 0x1EC41000, - VADDR_SPI = 0x1EC42000, - VADDR_SPI_2 = 0x1EC43000, // Only used under TWL_FIRM? - VADDR_I2C = 0x1EC44000, - VADDR_CODEC_2 = 0x1EC45000, - VADDR_HID = 0x1EC46000, - VADDR_PAD = 0x1EC46000, - VADDR_PTM = 0x1EC46000, - VADDR_GPIO = 0x1EC47000, - VADDR_I2C_2 = 0x1EC48000, - VADDR_SPI_3 = 0x1EC60000, - VADDR_I2C_3 = 0x1EC61000, - VADDR_MIC = 0x1EC62000, - VADDR_PXI = 0x1EC63000, // 0xFFFD2000 - //VADDR_NTRCARD - VADDR_CDMA = 0xFFFDA000, // CoreLink DMA-330? Info - VADDR_DSP_2 = 0x1ED03000, - VADDR_HASH_2 = 0x1EE01000, - VADDR_GPU = 0x1EF00000, -}; - template <typename T> inline void Read(T &var, const u32 addr) { switch (addr & 0xFFFFF000) { - case VADDR_GPU: GPU::Read(var, addr); break; - + case VADDR_LCD: + LCD::Write(var, addr); + break; default: LOG_ERROR(HW_Memory, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); } @@ -51,11 +27,12 @@ inline void Read(T &var, const u32 addr) { template <typename T> inline void Write(u32 addr, const T data) { switch (addr & 0xFFFFF000) { - case VADDR_GPU: GPU::Write(addr, data); break; - + case VADDR_LCD: + LCD::Write(addr, data); + break; default: LOG_ERROR(HW_Memory, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); } @@ -80,6 +57,7 @@ void Update() { /// Initialize hardware void Init() { GPU::Init(); + LCD::Init(); LOG_DEBUG(HW, "initialized OK"); } diff --git a/src/core/hw/hw.h b/src/core/hw/hw.h index 991c0a07d..d65608910 100644 --- a/src/core/hw/hw.h +++ b/src/core/hw/hw.h @@ -8,6 +8,30 @@ namespace HW { +/// Beginnings of IO register regions, in the user VA space. +enum : u32 { + VADDR_HASH = 0x1EC01000, + VADDR_CSND = 0x1EC03000, + VADDR_DSP = 0x1EC40000, + VADDR_PDN = 0x1EC41000, + VADDR_CODEC = 0x1EC41000, + VADDR_SPI = 0x1EC42000, + VADDR_SPI_2 = 0x1EC43000, // Only used under TWL_FIRM? + VADDR_I2C = 0x1EC44000, + VADDR_CODEC_2 = 0x1EC45000, + VADDR_HID = 0x1EC46000, + VADDR_GPIO = 0x1EC47000, + VADDR_I2C_2 = 0x1EC48000, + VADDR_SPI_3 = 0x1EC60000, + VADDR_I2C_3 = 0x1EC61000, + VADDR_MIC = 0x1EC62000, + VADDR_PXI = 0x1EC63000, + VADDR_LCD = 0x1ED02000, + VADDR_DSP_2 = 0x1ED03000, + VADDR_HASH_2 = 0x1EE01000, + VADDR_GPU = 0x1EF00000, +}; + template <typename T> void Read(T &var, const u32 addr); diff --git a/src/core/hw/lcd.cpp b/src/core/hw/lcd.cpp new file mode 100644 index 000000000..7986f3ddb --- /dev/null +++ b/src/core/hw/lcd.cpp @@ -0,0 +1,66 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/common_types.h" + +#include "core/arm/arm_interface.h" +#include "core/hle/hle.h" +#include "core/hw/hw.h" +#include "core/hw/lcd.h" + +namespace LCD { + +Regs g_regs; + +template <typename T> +inline void Read(T &var, const u32 raw_addr) { + u32 addr = raw_addr - HW::VADDR_LCD; + u32 index = addr / 4; + + // Reads other than u32 are untested, so I'd rather have them abort than silently fail + if (index >= 0x400 || !std::is_same<T, u32>::value) { + LOG_ERROR(HW_LCD, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); + return; + } + + var = g_regs[index]; +} + +template <typename T> +inline void Write(u32 addr, const T data) { + addr -= HW::VADDR_LCD; + u32 index = addr / 4; + + // Writes other than u32 are untested, so I'd rather have them abort than silently fail + if (index >= 0x400 || !std::is_same<T, u32>::value) { + LOG_ERROR(HW_LCD, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); + return; + } + + g_regs[index] = static_cast<u32>(data); +} + +// Explicitly instantiate template functions because we aren't defining this in the header: + +template void Read<u64>(u64 &var, const u32 addr); +template void Read<u32>(u32 &var, const u32 addr); +template void Read<u16>(u16 &var, const u32 addr); +template void Read<u8>(u8 &var, const u32 addr); + +template void Write<u64>(u32 addr, const u64 data); +template void Write<u32>(u32 addr, const u32 data); +template void Write<u16>(u32 addr, const u16 data); +template void Write<u8>(u32 addr, const u8 data); + +/// Initialize hardware +void Init() { + LOG_DEBUG(HW_LCD, "initialized OK"); +} + +/// Shutdown hardware +void Shutdown() { + LOG_DEBUG(HW_LCD, "shutdown OK"); +} + +} // namespace diff --git a/src/core/hw/lcd.h b/src/core/hw/lcd.h new file mode 100644 index 000000000..43893a625 --- /dev/null +++ b/src/core/hw/lcd.h @@ -0,0 +1,88 @@ +// Copyright 2015 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> + +#include "common/common_types.h" +#include "common/bit_field.h" + +#define LCD_REG_INDEX(field_name) (offsetof(LCD::Regs, field_name) / sizeof(u32)) + +namespace LCD { + +struct Regs { + + union ColorFill { + u32 raw; + + BitField<0, 8, u32> color_r; + BitField<8, 8, u32> color_g; + BitField<16, 8, u32> color_b; + BitField<24, 1, u32> is_enabled; + }; + + INSERT_PADDING_WORDS(0x81); + ColorFill color_fill_top; + INSERT_PADDING_WORDS(0xE); + u32 backlight_top; + + INSERT_PADDING_WORDS(0x1F0); + + ColorFill color_fill_bottom; + INSERT_PADDING_WORDS(0xE); + u32 backlight_bottom; + INSERT_PADDING_WORDS(0x16F); + + static inline size_t NumIds() { + return sizeof(Regs) / sizeof(u32); + } + + u32& operator [] (int index) const { + u32* content = (u32*)this; + return content[index]; + } + + u32& operator [] (int index) { + u32* content = (u32*)this; + return content[index]; + } + +#undef ASSERT_MEMBER_SIZE + +}; +static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout"); + +// TODO: MSVC does not support using offsetof() on non-static data members even though this +// is technically allowed since C++11. This macro should be enabled once MSVC adds +// support for that. +#ifndef _MSC_VER +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(Regs, field_name) == position * 4, \ + "Field "#field_name" has invalid position") + +ASSERT_REG_POSITION(color_fill_top, 0x81); +ASSERT_REG_POSITION(backlight_top, 0x90); +ASSERT_REG_POSITION(color_fill_bottom, 0x281); +ASSERT_REG_POSITION(backlight_bottom, 0x290); + +#undef ASSERT_REG_POSITION +#endif // !defined(_MSC_VER) + +extern Regs g_regs; + +template <typename T> +void Read(T &var, const u32 addr); + +template <typename T> +void Write(u32 addr, const T data); + +/// Initialize hardware +void Init(); + +/// Shutdown hardware +void Shutdown(); + +} // namespace diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 9ae2de99f..f6f670060 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -20,8 +20,8 @@ struct NCCH_Header { u16 version; u8 reserved_0[4]; u8 program_id[8]; - u8 temp_flag; - u8 reserved_1[0x2f]; + u8 reserved_1[0x10]; + u8 logo_region_hash[0x20]; u8 product_code[0x10]; u8 extended_header_hash[0x20]; u32 extended_header_size; @@ -29,15 +29,16 @@ struct NCCH_Header { u8 flags[8]; u32 plain_region_offset; u32 plain_region_size; - u8 reserved_3[8]; + u32 logo_region_offset; + u32 logo_region_size; u32 exefs_offset; u32 exefs_size; u32 exefs_hash_region_size; - u8 reserved_4[4]; + u8 reserved_3[4]; u32 romfs_offset; u32 romfs_size; u32 romfs_hash_region_size; - u8 reserved_5[4]; + u8 reserved_4[4]; u8 exefs_super_block_hash[0x20]; u8 romfs_super_block_hash[0x20]; }; @@ -88,8 +89,7 @@ struct ExHeader_DependencyList{ }; struct ExHeader_SystemInfo{ - u32 save_data_size; - u8 reserved[4]; + u64 save_data_size; u8 jump_id[8]; u8 reserved_2[0x30]; }; @@ -104,11 +104,14 @@ struct ExHeader_StorageInfo{ struct ExHeader_ARM11_SystemLocalCaps{ u8 program_id[8]; - u8 flags[8]; - u8 resource_limit_descriptor[0x10][2]; + u32 core_version; + u8 flags[3]; + u8 priority; + u8 resource_limit_descriptor[0x16][2]; ExHeader_StorageInfo storage_info; - u8 service_access_control[0x20][8]; - u8 reserved[0x1f]; + u8 service_access_control[0x32][8]; + u8 ex_service_access_control[0x2][8]; + u8 reserved[0xf]; u8 resource_limit_category; }; diff --git a/src/video_core/color.h b/src/video_core/color.h index 14ade74f2..43d635e2c 100644 --- a/src/video_core/color.h +++ b/src/video_core/color.h @@ -124,7 +124,7 @@ inline u32 DecodeD24(const u8* bytes) { * @return Resulting values stored as a Math::Vec2 */ inline const Math::Vec2<u32> DecodeD24S8(const u8* bytes) { - return { (bytes[2] << 16) | (bytes[1] << 8) | bytes[0], bytes[3] }; + return { static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3] }; } /** diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 745c4f4ed..83982b4f2 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -322,7 +322,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::RGBA8: { auto res = Color::DecodeRGBA8(source + VideoCore::GetMortonOffset(x, y, 4)); - return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; + return { res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a()) }; } case Regs::TextureFormat::RGB8: @@ -334,7 +334,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::RGB5A1: { auto res = Color::DecodeRGB5A1(source + VideoCore::GetMortonOffset(x, y, 2)); - return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; + return { res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a()) }; } case Regs::TextureFormat::RGB565: @@ -346,7 +346,7 @@ const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const Texture case Regs::TextureFormat::RGBA4: { auto res = Color::DecodeRGBA4(source + VideoCore::GetMortonOffset(x, y, 2)); - return { res.r(), res.g(), res.b(), disable_alpha ? 255 : res.a() }; + return { res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a()) }; } case Regs::TextureFormat::IA8: diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 95ab96340..4273a177f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include "core/hw/gpu.h" +#include "core/hw/hw.h" +#include "core/hw/lcd.h" #include "core/mem_map.h" #include "common/emu_window.h" @@ -64,16 +66,33 @@ void RendererOpenGL::SwapBuffers() { for(int i : {0, 1}) { const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; - if (textures[i].width != (GLsizei)framebuffer.width || - textures[i].height != (GLsizei)framebuffer.height || - textures[i].format != framebuffer.color_format) { - // Reallocate texture if the framebuffer size has changed. - // This is expected to not happen very often and hence should not be a - // performance problem. - ConfigureFramebufferTexture(textures[i], framebuffer); + // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 + u32 lcd_color_addr = (i == 0) ? LCD_REG_INDEX(color_fill_top) : LCD_REG_INDEX(color_fill_bottom); + lcd_color_addr = HW::VADDR_LCD + 4 * lcd_color_addr; + LCD::Regs::ColorFill color_fill = {0}; + LCD::Read(color_fill.raw, lcd_color_addr); + + if (color_fill.is_enabled) { + LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, textures[i]); + + // Resize the texture in case the framebuffer size has changed + textures[i].width = 1; + textures[i].height = 1; + } else { + if (textures[i].width != (GLsizei)framebuffer.width || + textures[i].height != (GLsizei)framebuffer.height || + textures[i].format != framebuffer.color_format) { + // Reallocate texture if the framebuffer size has changed. + // This is expected to not happen very often and hence should not be a + // performance problem. + ConfigureFramebufferTexture(textures[i], framebuffer); + } + LoadFBToActiveGLTexture(framebuffer, textures[i]); + + // Resize the texture in case the framebuffer size has changed + textures[i].width = framebuffer.width; + textures[i].height = framebuffer.height; } - - LoadFBToActiveGLTexture(GPU::g_regs.framebuffer_config[i], textures[i]); } DrawScreens(); @@ -127,10 +146,25 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& // TODO: Applications could theoretically crash Citra here by specifying too large // framebuffer sizes. We should make sure that this cannot happen. glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, - texture.gl_format, texture.gl_type, framebuffer_data); + texture.gl_format, texture.gl_type, framebuffer_data); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glBindTexture(GL_TEXTURE_2D, 0); +} +/** + * Fills active OpenGL texture with the given RGB color. + * Since the color is solid, the texture can be 1x1 but will stretch across whatever it's rendered on. + * This has the added benefit of being *really fast*. + */ +void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, + const TextureInfo& texture) { + glBindTexture(GL_TEXTURE_2D, texture.handle); + + u8 framebuffer_data[3] = { color_r, color_g, color_b }; + + // Update existing texture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index bcabab557..cd782428e 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -58,6 +58,9 @@ private: // Loads framebuffer from emulated memory into the active OpenGL texture. static void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, const TextureInfo& texture); + // Fills active OpenGL texture with the given RGB color. + static void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, + const TextureInfo& texture); /// Computes the viewport rectangle MathUtil::Rectangle<unsigned> GetViewportExtent(); @@ -72,7 +75,7 @@ private: GLuint vertex_array_handle; GLuint vertex_buffer_handle; GLuint program_id; - std::array<TextureInfo, 2> textures; + std::array<TextureInfo, 2> textures; ///< Textures for top and bottom screens respectively // Shader uniform location indices GLuint uniform_modelview_matrix; GLuint uniform_color_texture; diff --git a/src/video_core/vertex_shader.cpp b/src/video_core/vertex_shader.cpp index 4eb3e743e..e8d865172 100644 --- a/src/video_core/vertex_shader.cpp +++ b/src/video_core/vertex_shader.cpp @@ -72,7 +72,7 @@ struct VertexShaderState { u32* program_counter; const float24* input_register_table[16]; - float24* output_register_table[7*4]; + Math::Vec4<float24> output_registers[16]; Math::Vec4<float24> temporary_registers[16]; bool conditional_code[2]; @@ -198,8 +198,7 @@ static void ProcessShaderCode(VertexShaderState& state) { src2[3] = src2[3] * float24::FromFloat32(-1); } - float24* dest = (instr.common.dest.Value() < 0x08) ? state.output_register_table[4*instr.common.dest.Value().GetIndex()] - : (instr.common.dest.Value() < 0x10) ? dummy_vec4_float24 + float24* dest = (instr.common.dest.Value() < 0x10) ? &state.output_registers[instr.common.dest.Value().GetIndex()][0] : (instr.common.dest.Value() < 0x20) ? &state.temporary_registers[instr.common.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -409,8 +408,7 @@ static void ProcessShaderCode(VertexShaderState& state) { src3[3] = src3[3] * float24::FromFloat32(-1); } - float24* dest = (instr.mad.dest.Value() < 0x08) ? state.output_register_table[4*instr.mad.dest.Value().GetIndex()] - : (instr.mad.dest.Value() < 0x10) ? dummy_vec4_float24 + float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.output_registers[instr.mad.dest.Value().GetIndex()][0] : (instr.mad.dest.Value() < 0x20) ? &state.temporary_registers[instr.mad.dest.Value().GetIndex()][0] : dummy_vec4_float24; @@ -587,12 +585,18 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes) { if(num_attributes > 14) state.input_register_table[attribute_register_map.attribute14_register] = &input.attr[14].x; if(num_attributes > 15) state.input_register_table[attribute_register_map.attribute15_register] = &input.attr[15].x; - // Setup output register table - OutputVertex ret; - // Zero output so that attributes which aren't output won't have denormals in them, which will - // slow us down later. - memset(&ret, 0, sizeof(ret)); + state.conditional_code[0] = false; + state.conditional_code[1] = false; + + ProcessShaderCode(state); + DebugUtils::DumpShader(shader_memory.data(), state.debug.max_offset, swizzle_data.data(), + state.debug.max_opdesc_id, registers.vs_main_offset, + registers.vs_output_attributes); + // Setup output data + OutputVertex ret; + // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to + // figure out what those circumstances are and enable the remaining outputs then. for (int i = 0; i < 7; ++i) { const auto& output_register_map = registers.vs_output_attributes[i]; @@ -601,18 +605,18 @@ OutputVertex RunShader(const InputVertex& input, int num_attributes) { output_register_map.map_z, output_register_map.map_w }; - for (int comp = 0; comp < 4; ++comp) - state.output_register_table[4*i+comp] = ((float24*)&ret) + semantics[comp]; + for (int comp = 0; comp < 4; ++comp) { + float24* out = ((float24*)&ret) + semantics[comp]; + if (semantics[comp] != Regs::VSOutputAttributes::INVALID) { + *out = state.output_registers[i][comp]; + } else { + // Zero output so that attributes which aren't output won't have denormals in them, + // which would slow us down later. + memset(out, 0, sizeof(*out)); + } + } } - state.conditional_code[0] = false; - state.conditional_code[1] = false; - - ProcessShaderCode(state); - DebugUtils::DumpShader(shader_memory.data(), state.debug.max_offset, swizzle_data.data(), - state.debug.max_opdesc_id, registers.vs_main_offset, - registers.vs_output_attributes); - LOG_TRACE(Render_Software, "Output vertex: pos (%.2f, %.2f, %.2f, %.2f), col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f)", ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(), ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(), |