From 7c87a38a6aa649d212d55bfea143e690c160b91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=B6ppner?= Date: Wed, 12 Aug 2026 22:44:54 +0200 Subject: [PATCH] scaffolding for framebuffer functions --- core/include/nova64_core.h | 81 +------------ core/include/nova64_framebuffer.h | 3 +- core/include/nova64_internal.h | 1 + core/include/nova64_io_interface.h | 73 ++++++++++++ core/include/nova64_ppu_interface.h | 172 ++++++++++++++++++++++++++++ core/src/nova64_core.c | 22 ++-- core/src/nova64_framebuffer.c | 9 +- simulator/Nova64Core.cs | 86 +++++++++++++- simulator/Program.cs | 50 +++++--- 9 files changed, 384 insertions(+), 113 deletions(-) create mode 100644 core/include/nova64_io_interface.h create mode 100644 core/include/nova64_ppu_interface.h diff --git a/core/include/nova64_core.h b/core/include/nova64_core.h index 7eea708..2bc6afb 100644 --- a/core/include/nova64_core.h +++ b/core/include/nova64_core.h @@ -1,6 +1,8 @@ #ifndef NOVA64_CORE_H #define NOVA64_CORE_H +#include "nova64_io_interface.h" +#include "nova64_ppu_interface.h" #include #include #include @@ -21,82 +23,6 @@ #endif #endif -/* ========================================================================== */ -/* I/O Callback Typedefs */ -/* ========================================================================== */ - -/** - * @brief Callback to flush a frame buffer to the display. - * @param buffer Pointer to raw pixel data (e.g., RGB565 / RGBA8888). - * @param width Frame width in pixels. - * @param height Frame height in pixels. - */ -typedef void (*nova64_display_flush_cb_t)(const uint16_t *buffer, - uint16_t width, uint16_t height); - -/** - * @brief Callback to output audio samples. - * @param samples Pointer to PCM audio sample buffer. - * @param count Number of samples in the buffer. - */ -typedef void (*nova64_audio_output_cb_t)(const int16_t *samples, - uint32_t count); - -/** - * @brief Callback to read data from the SD card. - * @param slot_idx Zero-based cartridge slot index. - * @param file_path Null-terminated path to the file to read. - * @param destination_buffer Pointer to memory where file data should be copied. - * @param destination_length Maximum number of bytes available in the - * destination buffer. - * @return Number of bytes read on success, negative value on read error. - */ -typedef int32_t (*nova64_sd_read_file_cb_t)(uint32_t slot_idx, - const char *file_path, - uint8_t *destination_buffer, - uint32_t destination_length); - -/** - * @brief Callback to retrieve the size of a file on the SD card. - * @param slot_idx Zero-based cartridge slot index. - * @param file_path Null-terminated path to the file. - * @return File size in bytes on success, negative value on error. - */ -typedef int32_t (*nova64_sd_get_file_size_cb_t)(uint32_t slot_idx, - const char *file_path); - -/** - * @brief Callback triggered when a cartridge state change occurs (e.g. - * inserted/removed). - * @param slot_idx Zero-based cartridge slot index. - * @param is_inserted True if a cartridge is detected, false if ejected. - */ -typedef void (*nova64_cartridge_status_cb_t)(uint32_t slot_idx, - bool is_inserted); - -/** - * @brief Callback for core log output. - * @param message Null-terminated UTF-8 log string. - */ -typedef void (*nova64_log_cb_t)(const char *message); - -/* ========================================================================== */ -/* I/O Interface Configuration Struct */ -/* ========================================================================== */ - -/** - * @brief Struct containing all I/O hardware abstraction callbacks. - * Pass NULL for any unneeded callback. - */ -typedef struct { - nova64_display_flush_cb_t display_flush; - nova64_audio_output_cb_t audio_output; - nova64_sd_read_file_cb_t sd_read_file; - nova64_sd_get_file_size_cb_t sd_get_file_size; - nova64_cartridge_status_cb_t cartridge_status; - nova64_log_cb_t log_message; -} nova64_io_interface_t; - /* ========================================================================== */ /* Engine Core API */ /* ========================================================================== */ @@ -109,7 +35,8 @@ typedef struct { * @return True if initialized successfully, false if already initialized or * invalid. */ -NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface); +NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface, + const nova64_ppu_interface_t *ppu_interface); /** * @brief Starts the main engine runtime and launches the FreeRTOS scheduler. diff --git a/core/include/nova64_framebuffer.h b/core/include/nova64_framebuffer.h index d662218..6898560 100644 --- a/core/include/nova64_framebuffer.h +++ b/core/include/nova64_framebuffer.h @@ -2,6 +2,7 @@ #define NOVA64_FRAMEBUFFER_H #include "nova64_core.h" +#include "nova64_ppu_interface.h" #include #include #include @@ -11,8 +12,6 @@ #define NOVA64_DISPLAY_HEIGHT 800 #define NOVA64_DISPLAY_PIXELS (NOVA64_DISPLAY_WIDTH * NOVA64_DISPLAY_HEIGHT) -// Pixel format: RGB565 (uint16_t) -typedef uint16_t pixel_t; // Framebuffer size in bytes (768,000 bytes) #define NOVA64_FRAMEBUFFER_SIZE_BYTES (NOVA64_DISPLAY_PIXELS * sizeof(pixel_t)) diff --git a/core/include/nova64_internal.h b/core/include/nova64_internal.h index 3a86202..ea3bbf7 100644 --- a/core/include/nova64_internal.h +++ b/core/include/nova64_internal.h @@ -6,6 +6,7 @@ void nova64_log(const char *format, ...); const nova64_io_interface_t *nova64_get_io_interface(void); +const nova64_ppu_interface_t *nova64_get_ppu_interface(void); void nova64_log_scheduler_state(void); diff --git a/core/include/nova64_io_interface.h b/core/include/nova64_io_interface.h new file mode 100644 index 0000000..98f1f99 --- /dev/null +++ b/core/include/nova64_io_interface.h @@ -0,0 +1,73 @@ +#ifndef NOVA64_IO_INTERFACE_H +#define NOVA64_IO_INTERFACE_H + +#include +#include + +/* ========================================================================== */ +/* I/O Callback Typedefs */ +/* ========================================================================== */ + +/** + * @brief Callback to output audio samples. + * @param samples Pointer to PCM audio sample buffer. + * @param count Number of samples in the buffer. + */ +typedef void (*nova64_audio_output_cb_t)(const int16_t *samples, + uint32_t count); + +/** + * @brief Callback to read data from the SD card. + * @param slot_idx Zero-based cartridge slot index. + * @param file_path Null-terminated path to the file to read. + * @param destination_buffer Pointer to memory where file data should be copied. + * @param destination_length Maximum number of bytes available in the + * destination buffer. + * @return Number of bytes read on success, negative value on read error. + */ +typedef int32_t (*nova64_sd_read_file_cb_t)(uint32_t slot_idx, + const char *file_path, + uint8_t *destination_buffer, + uint32_t destination_length); + +/** + * @brief Callback to retrieve the size of a file on the SD card. + * @param slot_idx Zero-based cartridge slot index. + * @param file_path Null-terminated path to the file. + * @return File size in bytes on success, negative value on error. + */ +typedef int32_t (*nova64_sd_get_file_size_cb_t)(uint32_t slot_idx, + const char *file_path); + +/** + * @brief Callback triggered when a cartridge state change occurs (e.g. + * inserted/removed). + * @param slot_idx Zero-based cartridge slot index. + * @param is_inserted True if a cartridge is detected, false if ejected. + */ +typedef void (*nova64_cartridge_status_cb_t)(uint32_t slot_idx, + bool is_inserted); + +/** + * @brief Callback for core log output. + * @param message Null-terminated UTF-8 log string. + */ +typedef void (*nova64_log_cb_t)(const char *message); + +/* ========================================================================== */ +/* I/O Interface Configuration Struct */ +/* ========================================================================== */ + +/** + * @brief Struct containing all I/O hardware abstraction callbacks. + * Pass NULL for any unneeded callback. + */ +typedef struct { + nova64_audio_output_cb_t audio_output; + nova64_sd_read_file_cb_t sd_read_file; + nova64_sd_get_file_size_cb_t sd_get_file_size; + nova64_cartridge_status_cb_t cartridge_status; + nova64_log_cb_t log_message; +} nova64_io_interface_t; + +#endif // NOVA64_IO_INTERFACE_H diff --git a/core/include/nova64_ppu_interface.h b/core/include/nova64_ppu_interface.h new file mode 100644 index 0000000..f0a0a82 --- /dev/null +++ b/core/include/nova64_ppu_interface.h @@ -0,0 +1,172 @@ +#ifndef NOVA64_PPU_INTERFACE_H +#define NOVA64_PPU_INTERFACE_H + +#include +#include + + +// Pixel format: RGB565 (uint16_t) +typedef uint16_t pixel_t; + +/* ========================================================================== */ +/* PPU Callback Typedefs */ +/* ========================================================================== */ + +/** + * @brief Transformation flags for hardware-accelerated sprite operations. + */ +typedef enum { + NOVA64_TRANSFORM_NONE = 0, + NOVA64_TRANSFORM_FLIP_H = 1 << 0, + NOVA64_TRANSFORM_FLIP_V = 1 << 1, + NOVA64_TRANSFORM_ROT_90 = 1 << 2, + NOVA64_TRANSFORM_ROT_180 = 1 << 3, + NOVA64_TRANSFORM_ROT_270 = 1 << 4 +} nova64_transform_t; + +/** + * @brief Callback to perform a solid color rectangle fill (e.g. clear/rects). + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride (pitch) of destination buffer in pixels. + * @param x Top-left X position in destination buffer. + * @param y Top-left Y position in destination buffer. + * @param w Width of rectangle to fill. + * @param h Height of rectangle to fill. + * @param color 16-bit RGB565 pixel color value. + */ +typedef void (*nova64_ppu_fill_rect_cb_t)(pixel_t *dst, uint32_t dst_stride, + int32_t x, int32_t y, int32_t w, + int32_t h, pixel_t color); + +/** + * @brief Callback for hardware-accelerated block image transfer (opaque Blit). + * @param src Pointer to source surface buffer. + * @param src_stride Row stride (pitch) of source surface in pixels. + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride (pitch) of destination buffer in pixels. + * @param src_x X coordinate on source surface. + * @param src_y Y coordinate on source surface. + * @param dst_x X coordinate on destination buffer. + * @param dst_y Y coordinate on destination buffer. + * @param w Width of block to transfer. + * @param h Height of block to transfer. + */ +typedef void (*nova64_ppu_blit_cb_t)(const pixel_t *src, uint32_t src_stride, + pixel_t *dst, uint32_t dst_stride, + int32_t src_x, int32_t src_y, + int32_t dst_x, int32_t dst_y, int32_t w, + int32_t h); + +/** + * @brief Callback for transparent sprite blitting using color keying. + * @param src Pointer to source surface buffer. + * @param src_stride Row stride of source surface in pixels. + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride of destination buffer in pixels. + * @param src_x X coordinate on source surface. + * @param src_y Y coordinate on source surface. + * @param dst_x X coordinate on destination buffer. + * @param dst_y Y coordinate on destination buffer. + * @param w Width of block to transfer. + * @param h Height of block to transfer. + * @param key_color 16-bit color treated as fully transparent. + */ +typedef void (*nova64_ppu_blit_colorkey_cb_t)( + const pixel_t *src, uint32_t src_stride, pixel_t *dst, uint32_t dst_stride, + int32_t src_x, int32_t src_y, int32_t dst_x, int32_t dst_y, int32_t w, + int32_t h, pixel_t key_color); + +/** + * @brief Callback for alpha-blended surface composition. + * @param src Pointer to source surface buffer. + * @param src_stride Row stride of source surface in pixels. + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride of destination buffer in pixels. + * @param src_x X coordinate on source surface. + * @param src_y Y coordinate on source surface. + * @param dst_x X coordinate on destination buffer. + * @param dst_y Y coordinate on destination buffer. + * @param w Width of block to transfer. + * @param h Height of block to transfer. + * @param global_alpha Global opacity multiplier (0 = transparent, 255 = + * opaque). + */ +typedef void (*nova64_ppu_blend_cb_t)(const pixel_t *src, uint32_t src_stride, + pixel_t *dst, uint32_t dst_stride, + int32_t src_x, int32_t src_y, + int32_t dst_x, int32_t dst_y, int32_t w, + int32_t h, uint8_t global_alpha); + +/** + * @brief Callback for transformed image transfer (rotation & flipping). + * @param src Pointer to source surface buffer. + * @param src_stride Row stride of source surface in pixels. + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride of destination buffer in pixels. + * @param src_x X coordinate on source surface. + * @param src_y Y coordinate on source surface. + * @param dst_x X coordinate on destination buffer. + * @param dst_y Y coordinate on destination buffer. + * @param w Width of block to transfer. + * @param h Height of block to transfer. + * @param transform Rotation/Flip flags to apply during transfer. + */ +typedef void (*nova64_ppu_blit_transform_cb_t)( + const pixel_t *src, uint32_t src_stride, pixel_t *dst, uint32_t dst_stride, + int32_t src_x, int32_t src_y, int32_t dst_x, int32_t dst_y, int32_t w, + int32_t h, nova64_transform_t transform); + +/** + * @brief Callback for hardware-accelerated image scaling/resizing. + * @param src Pointer to source surface buffer. + * @param src_stride Row stride of source surface in pixels. + * @param src_w Width of source rectangle. + * @param src_h Height of source rectangle. + * @param dst Pointer to destination buffer memory. + * @param dst_stride Row stride of destination buffer in pixels. + * @param dst_x Target X coordinate on destination buffer. + * @param dst_y Target Y coordinate on destination buffer. + * @param dst_w Target scaled width on destination buffer. + * @param dst_h Target scaled height on destination buffer. + */ +typedef void (*nova64_ppu_blit_scale_cb_t)(const pixel_t *src, + uint32_t src_stride, int32_t src_w, + int32_t src_h, pixel_t *dst, + uint32_t dst_stride, int32_t dst_x, + int32_t dst_y, int32_t dst_w, + int32_t dst_h); + +/** + * @brief Callback to block CPU execution until all PPU/DMA2D hardware + * operations finish. + */ +typedef void (*nova64_ppu_wait_idle_cb_t)(void); + +/** + * @brief Callback to check if the PPU/DMA2D engine is currently processing a + * job. + * @return True if hardware is busy, false if idle. + */ +typedef bool (*nova64_ppu_is_busy_cb_t)(void); + +/* ========================================================================== */ +/* PPU Interface Configuration Struct */ +/* ========================================================================== */ + +/** + * @brief Struct containing all PPU hardware abstraction callbacks. + * Pass NULL for any unsupported or unneeded hardware acceleration + * feature. + */ +typedef struct { + nova64_ppu_fill_rect_cb_t fill_rect; + nova64_ppu_blit_cb_t blit; + nova64_ppu_blit_colorkey_cb_t blit_colorkey; + nova64_ppu_blend_cb_t blend; + nova64_ppu_blit_transform_cb_t blit_transform; + nova64_ppu_blit_scale_cb_t blit_scale; + nova64_ppu_wait_idle_cb_t wait_idle; + nova64_ppu_is_busy_cb_t is_busy; +} nova64_ppu_interface_t; + +#endif // NOVA64_PPU_INTERFACE_H \ No newline at end of file diff --git a/core/src/nova64_core.c b/core/src/nova64_core.c index 895c6b8..9e55fcc 100644 --- a/core/src/nova64_core.c +++ b/core/src/nova64_core.c @@ -23,8 +23,8 @@ volatile bool is_initialized = false; volatile bool shutdown_requested = false; -static nova64_io_interface_t g_io_interface_storage; -static const nova64_io_interface_t *g_io_interface = NULL; +static const nova64_io_interface_t *nova64_io_interface = NULL; +static const nova64_ppu_interface_t *nova64_ppu_interface = NULL; static QueueHandle_t event_queue = NULL; static TaskHandle_t heartbeat_task_handle = NULL; @@ -72,8 +72,8 @@ void nova64_log(const char *format, ...) { log_lock(); - if (g_io_interface && g_io_interface->log_message) { - g_io_interface->log_message(buffer); + if (nova64_io_interface && nova64_io_interface->log_message) { + nova64_io_interface->log_message(buffer); } else { fputs(buffer, stdout); fflush(stdout); @@ -134,16 +134,20 @@ void start_heartbeat_task(void) { } const nova64_io_interface_t *nova64_get_io_interface(void) { - return g_io_interface; + return nova64_io_interface; } -NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) { - if (is_initialized || io_interface == NULL) { +const nova64_ppu_interface_t *nova64_get_ppu_interface(void) { + return nova64_ppu_interface; +} + +NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface, const nova64_ppu_interface_t *ppu_interface) { + if (is_initialized || io_interface == NULL || ppu_interface == NULL) { return false; } - g_io_interface_storage = *io_interface; - g_io_interface = &g_io_interface_storage; + nova64_io_interface = io_interface; + nova64_ppu_interface = ppu_interface; nova64_log("System Initializing...\n"); diff --git a/core/src/nova64_framebuffer.c b/core/src/nova64_framebuffer.c index 8ce8510..bb0ba1c 100644 --- a/core/src/nova64_framebuffer.c +++ b/core/src/nova64_framebuffer.c @@ -84,11 +84,10 @@ void nova64_framebuffer_swap(void) { } void nova64_framebuffer_clear(pixel_t color) { - uint32_t *p32 = (uint32_t *)nova64_framebuffer.back_buffer; - uint32_t color32 = ((uint32_t)color << 16) | color; - size_t count32 = NOVA64_DISPLAY_PIXELS / 2; + const nova64_ppu_interface_t *ppu = nova64_get_ppu_interface(); - for (size_t i = 0; i < count32; i++) { - p32[i] = color32; + if (ppu && ppu->fill_rect) { + ppu->fill_rect(nova64_framebuffer.back_buffer, NOVA64_DISPLAY_WIDTH, 0, 0, + NOVA64_DISPLAY_WIDTH, NOVA64_DISPLAY_HEIGHT, color); } } \ No newline at end of file diff --git a/simulator/Nova64Core.cs b/simulator/Nova64Core.cs index 046bdfb..da446b6 100644 --- a/simulator/Nova64Core.cs +++ b/simulator/Nova64Core.cs @@ -1,9 +1,75 @@ +using System; using System.Runtime.InteropServices; -internal static class Nova64Core +internal static class Nova64 { private const string LibName = "nova64_core"; + /* ========================================================================== */ + /* Enums */ + /* ========================================================================== */ + + [Flags] + public enum Nova64Transform : uint + { + None = 0, + FlipH = 1 << 0, + FlipV = 1 << 1, + Rot90 = 1 << 2, + Rot180 = 1 << 3, + Rot270 = 1 << 4 + } + + /* ========================================================================== */ + /* PPU Delegates */ + /* ========================================================================== */ + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuFillRectDelegate(IntPtr dst, uint dstStride, int x, int y, int w, int h, ushort color); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuBlitDelegate(IntPtr src, uint srcStride, IntPtr dst, uint dstStride, int srcX, int srcY, int dstX, int dstY, int w, int h); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuBlitColorKeyDelegate(IntPtr src, uint srcStride, IntPtr dst, uint dstStride, int srcX, int srcY, int dstX, int dstY, int w, int h, ushort keyColor); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuBlendDelegate(IntPtr src, uint srcStride, IntPtr dst, uint dstStride, int srcX, int srcY, int dstX, int dstY, int w, int h, byte globalAlpha); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuBlitTransformDelegate(IntPtr src, uint srcStride, IntPtr dst, uint dstStride, int srcX, int srcY, int dstX, int dstY, int w, int h, Nova64Transform transform); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuBlitScaleDelegate(IntPtr src, uint srcStride, int srcW, int srcH, IntPtr dst, uint dstStride, int dstX, int dstY, int dstW, int dstH); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void PpuWaitIdleDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] + public delegate bool PpuIsBusyDelegate(); + + /* ========================================================================== */ + /* PPU Interface Struct */ + /* ========================================================================== */ + + [StructLayout(LayoutKind.Sequential)] + public struct Nova64PpuInterface + { + public IntPtr fill_rect; + public IntPtr blit; + public IntPtr blit_colorkey; + public IntPtr blend; + public IntPtr blit_transform; + public IntPtr blit_scale; + public IntPtr wait_idle; + public IntPtr is_busy; + } + + /* ========================================================================== */ + /* I/O Delegates */ + /* ========================================================================== */ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void DisplayFlushDelegate(IntPtr buffer, ushort width, ushort height); @@ -17,15 +83,18 @@ internal static class Nova64Core public delegate int SdGetFileSizeDelegate(uint slotIdx, string filePath); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void CartridgeStatusDelegate(uint slotIdx, bool isInserted); + public delegate void CartridgeStatusDelegate(uint slotIdx, [MarshalAs(UnmanagedType.I1)] bool isInserted); [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public delegate void LogMessageDelegate([MarshalAs(UnmanagedType.LPStr)] string message); + /* ========================================================================== */ + /* I/O Interface Struct */ + /* ========================================================================== */ + [StructLayout(LayoutKind.Sequential)] public struct Nova64IoInterface { - public IntPtr display_flush; public IntPtr audio_output; public IntPtr sd_read_file; public IntPtr sd_get_file_size; @@ -33,18 +102,25 @@ internal static class Nova64Core public IntPtr log_message; } + /* ========================================================================== */ + /* Native Imports */ + /* ========================================================================== */ + [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] - public static extern bool nova64_init(IntPtr io_interface); + [return: MarshalAs(UnmanagedType.I1)] + public static extern bool nova64_init(IntPtr io_interface, IntPtr ppu_interface); [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] public static extern int nova64_main(); [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] public static extern bool nova64_shutdown(); [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I1)] public static extern bool nova64_is_finalized(); [DllImport(LibName, CallingConvention = CallingConvention.Cdecl)] public static extern uint nova64_get_version(); -} +} \ No newline at end of file diff --git a/simulator/Program.cs b/simulator/Program.cs index 49d5632..716af5d 100644 --- a/simulator/Program.cs +++ b/simulator/Program.cs @@ -13,9 +13,9 @@ internal static class Program return SdRoot / $"slot{slotIdx}" / normalizedPath; } - private static readonly Nova64Core.DisplayFlushDelegate DisplayFlush = (buffer, width, height) => { }; - private static readonly Nova64Core.AudioOutputDelegate AudioOutput = (samples, count) => { }; - private static readonly Nova64Core.SdReadFileDelegate SdReadFile = (slotIdx, filePath, destinationBuffer, destinationLength) => + private static readonly Nova64.DisplayFlushDelegate DisplayFlush = (buffer, width, height) => { }; + private static readonly Nova64.AudioOutputDelegate AudioOutput = (samples, count) => { }; + private static readonly Nova64.SdReadFileDelegate SdReadFile = (slotIdx, filePath, destinationBuffer, destinationLength) => { var path = GetSlotFilePath(slotIdx, filePath); Console.WriteLine($"[Core] SD read request: slot {slotIdx}, file '{filePath}', resolved path '{path}' destination length {destinationLength}"); @@ -47,7 +47,7 @@ internal static class Program return -1; } }; - private static readonly Nova64Core.SdGetFileSizeDelegate SdGetFileSize = (slotIdx, filePath) => + private static readonly Nova64.SdGetFileSizeDelegate SdGetFileSize = (slotIdx, filePath) => { var path = GetSlotFilePath(slotIdx, filePath); Console.WriteLine($"[Core] SD get file size request: slot {slotIdx}, file '{filePath}', resolved path '{path}'"); @@ -69,14 +69,15 @@ internal static class Program return -1; } }; - private static readonly Nova64Core.CartridgeStatusDelegate CartridgeStatus = (slotIdx, isInserted) => { }; - private static readonly Nova64Core.LogMessageDelegate LogMessage = (message) => + private static readonly Nova64.CartridgeStatusDelegate CartridgeStatus = (slotIdx, isInserted) => { }; + private static readonly Nova64.LogMessageDelegate LogMessage = (message) => { Console.Write($"[Core] {message}"); Console.Out.Flush(); }; private static GCHandle? _ioHandle; + private static GCHandle? _ppuHandle; private static Thread? _coreThread; private static readonly ManualResetEventSlim _coreStartedEvent = new(false); @@ -87,8 +88,13 @@ internal static class Program Console.WriteLine("[Sim] Core thread failed: IO handle missing."); return; } + if (_ppuHandle == null) + { + Console.WriteLine("[Sim] Core thread failed: PPU handle missing."); + return; + } - if (!Nova64Core.nova64_init(_ioHandle.Value.AddrOfPinnedObject())) + if (!Nova64.nova64_init(_ioHandle.Value.AddrOfPinnedObject(), _ppuHandle.Value.AddrOfPinnedObject())) { Console.WriteLine("[Sim] nova64_init failed in core thread."); _coreStartedEvent.Set(); @@ -97,7 +103,7 @@ internal static class Program _coreStartedEvent.Set(); - var result = Nova64Core.nova64_main(); + var result = Nova64.nova64_main(); Console.WriteLine($"[Sim] nova64_main returned: {result}"); } @@ -107,12 +113,11 @@ internal static class Program try { - var version = Nova64Core.nova64_get_version(); + var version = Nova64.nova64_get_version(); Console.WriteLine($"[Sim] Loaded nova64_core native library. Version: 0x{version:X}"); - var ioInterface = new Nova64Core.Nova64IoInterface + var ioInterface = new Nova64.Nova64IoInterface { - display_flush = Marshal.GetFunctionPointerForDelegate(DisplayFlush), audio_output = Marshal.GetFunctionPointerForDelegate(AudioOutput), sd_read_file = Marshal.GetFunctionPointerForDelegate(SdReadFile), sd_get_file_size = Marshal.GetFunctionPointerForDelegate(SdGetFileSize), @@ -121,6 +126,14 @@ internal static class Program }; _ioHandle = GCHandle.Alloc(ioInterface, GCHandleType.Pinned); + + var ppuInterface = new Nova64.Nova64PpuInterface + { + + }; + + _ppuHandle = GCHandle.Alloc(ppuInterface, GCHandleType.Pinned); + _coreThread = new Thread(CoreThreadProc) { IsBackground = true, @@ -143,10 +156,10 @@ internal static class Program if (_coreThread != null && _coreThread.IsAlive) { Console.WriteLine("[Sim] Requesting core shutdown..."); - if (Nova64Core.nova64_shutdown()) + if (Nova64.nova64_shutdown()) { int timeoutMs = 1000; - while (!Nova64Core.nova64_is_finalized() && timeoutMs > 0) + while (!Nova64.nova64_is_finalized() && timeoutMs > 0) { Thread.Sleep(10); timeoutMs -= 10; @@ -178,9 +191,16 @@ internal static class Program { Console.WriteLine("Core thread still running; leaving native handle pinned until process exit."); } - else if (_ioHandle.HasValue && _ioHandle.Value.IsAllocated) + else { - _ioHandle.Value.Free(); + if (_ioHandle.HasValue && _ioHandle.Value.IsAllocated) + { + _ioHandle.Value.Free(); + } + if (_ppuHandle.HasValue && _ppuHandle.Value.IsAllocated) + { + _ppuHandle.Value.Free(); + } } } }