Merge branch 'Sync-Objects' of https://github.com/wheremyfoodat/Virtual3DS into Sync-Objects

This commit is contained in:
wheremyfoodat 2023-01-10 00:37:27 +02:00
commit eae9bc67dd
50 changed files with 975 additions and 122 deletions

View file

@ -13,6 +13,7 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
include_directories(${PROJECT_SOURCE_DIR}/include/)
include_directories(${PROJECT_SOURCE_DIR}/include/kernel)
include_directories (${FMT_INCLUDE_DIR})
include_directories(third_party/boost/)
include_directories(third_party/elfio/)
include_directories(third_party/gl3w/)
include_directories(third_party/imgui/)
@ -57,12 +58,18 @@ set(SERVICE_SOURCE_FILES src/core/services/service_manager.cpp src/core/services
src/core/services/fs.cpp src/core/services/gsp_gpu.cpp src/core/services/gsp_lcd.cpp
src/core/services/ndm.cpp src/core/services/dsp.cpp src/core/services/cfg.cpp
src/core/services/ptm.cpp src/core/services/mic.cpp src/core/services/cecd.cpp
src/core/services/ac.cpp src/core/services/am.cpp src/core/services/boss.cpp
src/core/services/frd.cpp src/core/services/nim.cpp
)
set(PICA_SOURCE_FILES src/core/PICA/gpu.cpp src/core/PICA/regs.cpp src/core/PICA/shader_unit.cpp
src/core/PICA/shader_interpreter.cpp src/core/PICA/renderer_opengl.cpp
src/core/PICA/shader_interpreter.cpp
)
set(RENDERER_GL_SOURCE_FILES src/core/renderer_gl/renderer_gl.cpp)
set(LOADER_SOURCE_FILES src/core/loader/elf.cpp src/core/loader/ncsd.cpp src/core/loader/ncch.cpp)
set(FS_SOURCE_FILES src/core/fs/archive_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp)
set(FS_SOURCE_FILES src/core/fs/archive_ncch.cpp src/core/fs/archive_save_data.cpp src/core/fs/archive_sdmc.cpp
src/core/fs/archive_ext_save_data.cpp
)
set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp include/termcolor.hpp
include/cpu.hpp include/cpu_dynarmic.hpp include/memory.hpp include/kernel/kernel.hpp
@ -76,7 +83,10 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/opengl.hpp inc
include/loader/lz77.hpp include/fs/archive_base.hpp include/fs/archive_ncch.hpp
include/services/dsp.hpp include/services/cfg.hpp include/services/region_codes.hpp
include/fs/archive_save_data.hpp include/fs/archive_sdmc.hpp include/services/ptm.hpp
include/services/mic.hpp include/services/cecd.hpp
include/services/mic.hpp include/services/cecd.hpp include/renderer_gl/renderer_gl.hpp
include/renderer_gl/surfaces.hpp include/renderer_gl/surface_cache.hpp include/services/ac.hpp
include/services/am.hpp include/services/boss.hpp include/services/frd.hpp include/services/nim.hpp
include/fs/archive_ext_save_data.hpp
)
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
@ -95,8 +105,9 @@ source_group("Source Files\\Core\\Kernel" FILES ${KERNEL_SOURCE_FILES})
source_group("Source Files\\Core\\Loader" FILES ${LOADER_SOURCE_FILES})
source_group("Source Files\\Core\\Services" FILES ${SERVICE_SOURCE_FILES})
source_group("Source Files\\Core\\PICA" FILES ${PICA_SOURCE_FILES})
source_group("Source Files\\Core\\OpenGL Renderer" FILES ${RENDERER_GL_SOURCE_FILES})
source_group("Source Files\\Third Party" FILES ${THIRD_PARTY_SOURCE_FILES})
add_executable(Alber ${SOURCE_FILES} ${FS_SOURCE_FILES} ${KERNEL_SOURCE_FILES} ${LOADER_SOURCE_FILES} ${SERVICE_SOURCE_FILES}
${PICA_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
${PICA_SOURCE_FILES} ${RENDERER_GL_SOURCE_FILES} ${THIRD_PARTY_SOURCE_FILES} ${HEADER_FILES})
target_link_libraries(Alber PRIVATE dynarmic SDL2-static)

View file

@ -3,9 +3,9 @@
#include "helpers.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "opengl.hpp"
#include "PICA/float_types.hpp"
#include "PICA/shader_unit.hpp"
#include "renderer_gl/renderer_gl.hpp"
class GPU {
using vec4f = OpenGL::Vector<Floats::f24, 4>;
@ -20,11 +20,6 @@ class GPU {
static constexpr u32 vramSize = 6_MB;
std::array<u32, regNum> regs; // GPU internal registers
struct Vertex {
OpenGL::vec4 position;
OpenGL::vec4 colour;
};
// Read a value of type T from physical address paddr
// This is necessary because vertex attribute fetching uses physical addresses
template<typename T>
@ -80,32 +75,15 @@ class GPU {
u32 fixedAttribCount = 0; // How many attribute components have we written? When we get to 4 the attr will actually get submitted
std::array<u32, 3> fixedAttrBuff; // Buffer to hold fixed attributes in until they get submitted
// OpenGL renderer state
OpenGL::Framebuffer fbo;
OpenGL::Texture fboTexture;
OpenGL::Program triangleProgram;
OpenGL::Program displayProgram;
OpenGL::VertexArray vao;
OpenGL::VertexBuffer vbo;
GLint alphaControlLoc = -1;
u32 oldAlphaControl = 0;
// Dummy VAO/VBO for blitting the final output
OpenGL::VertexArray dummyVAO;
OpenGL::VertexBuffer dummyVBO;
static constexpr u32 vertexBufferSize = 0x1000;
void drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count);
Renderer renderer;
public:
GPU(Memory& mem);
void initGraphicsContext(); // Initialize graphics context
void getGraphicsContext(); // Set up the graphics context for rendering
void display(); // Display the screen contents onto our window
void initGraphicsContext() { renderer.initGraphicsContext(); }
void getGraphicsContext() { renderer.getGraphicsContext(); }
void display() { renderer.display(); }
void fireDMA(u32 dest, u32 source, u32 size);
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control);
void reset();
// Used by the GSP GPU service for readHwRegs/writeHwRegs/writeHwRegsMasked
@ -115,4 +93,8 @@ public:
// Used when processing GPU command lists
u32 readInternalReg(u32 index);
void writeInternalReg(u32 index, u32 value, u32 mask);
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
renderer.clearBuffer(startAddress, endAddress, value, control);
}
};

View file

@ -14,6 +14,10 @@ namespace PICAInternalRegs {
// Framebuffer registers
AlphaTestConfig = 0x104,
DepthAndColorMask = 0x107,
ColourBufferFormat = 0x117,
DepthBufferLoc = 0x11C,
ColourBufferLoc = 0x11D,
FramebufferSize = 0x11E,
// Geometry pipeline registers
VertexAttribLoc = 0x200,

View file

@ -18,6 +18,7 @@ namespace ShaderOpcodes {
MUL = 0x08,
MAX = 0x0C,
MIN = 0x0D,
RCP = 0x0E,
RSQ = 0x0F,
MOVA = 0x12,
MOV = 0x13,
@ -99,6 +100,7 @@ class PICAShader {
void mov(u32 instruction);
void mova(u32 instruction);
void mul(u32 instruction);
void rcp(u32 instruction);
void rsq(u32 instruction);
// src1, src2 and src3 have different negation & component swizzle bits in the operand descriptor

View file

@ -61,6 +61,7 @@ namespace FPSCR {
RoundToZero = 3 << 22,
// Default FPSCR value for threads
ThreadDefault = DefaultNan | FlushToZero | RoundToZero | IXC
ThreadDefault = DefaultNan | FlushToZero | RoundToZero,
MainThreadDefault = ThreadDefault | IXC
};
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "archive_base.hpp"
class ExtSaveDataArchive : public ArchiveBase {
public:
ExtSaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { Helpers::panic("ExtSaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "ExtSaveData"; }
bool openFile(const FSPath& path) override;
ArchiveBase* openArchive(const FSPath& path) override;
std::optional<u32> readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) override;
bool isShared = false;
};

View file

@ -2,7 +2,6 @@
#include "archive_base.hpp"
class SelfNCCHArchive : public ArchiveBase {
public:
SelfNCCHArchive(Memory& mem) : ArchiveBase(mem) {}

View file

@ -2,11 +2,10 @@
#include "archive_base.hpp"
class SaveDataArchive : public ArchiveBase {
public:
SaveDataArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
u64 getFreeBytes() override { Helpers::panic("SaveData::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SaveData"; }
bool openFile(const FSPath& path) override;

View file

@ -2,11 +2,10 @@
#include "archive_base.hpp"
class SDMCArchive : public ArchiveBase {
public:
SDMCArchive(Memory& mem) : ArchiveBase(mem) {}
u64 getFreeBytes() override { return 0; }
u64 getFreeBytes() override { Helpers::panic("SDMC::GetFreeBytes unimplemented"); return 0; }
std::string name() override { return "SDMC"; }
bool openFile(const FSPath& path) override;

View file

@ -6,9 +6,12 @@ namespace ConfigMem {
enum : u32 {
KernelVersionMinor = 0x1FF80002,
KernelVersionMajor = 0x1FF80003,
SyscoreVer = 0x1FF80010,
EnvInfo = 0x1FF80014,
AppMemAlloc = 0x1FF80040,
Datetime0 = 0x1FF81020,
LedState3D = 0x1FF81084
LedState3D = 0x1FF81084,
BatteryState = 0x1FF81085,
HeadphonesConnectedMaybe = 0x1FF810C0 // TODO: What is actually stored here?
};
}

View file

@ -10,19 +10,24 @@ namespace KernelHandles {
// Hardcoded handles
CurrentThread = 0xFFFF8000, // Used by the original kernel
CurrentProcess = 0xFFFF8001, // Used by the original kernel
AC, // Something network related
AM, // Application manager
APT, // App Title something service?
CECD, // Streetpass stuff?
BOSS, // Streetpass stuff?
CECD, // More Streetpass stuff?
CFG, // CFG service (Console & region info)
HID, // HID service (Handles everything input-related including gyro)
FRD, // Friend service (Miiverse friend service)
FS, // Filesystem service
GPU, // GPU service
DSP, // DSP service (Used for audio decoding and output)
LCD, // LCD service (Used for configuring the displays)
MIC, // MIC service (Controls the microphone)
NIM, // Updates, DLC, etc
NDM, // ?????
PTM, // PTM service (Used for accessing various console info, such as battery, shell and pedometer state)
MinServiceHandle = APT,
MinServiceHandle = AC,
MaxServiceHandle = PTM,
GSPSharedMemHandle = MaxServiceHandle + 1, // Handle for the GSP shared memory

View file

@ -81,6 +81,7 @@ private:
MAKE_LOG_FUNCTION(log, kernelLogger)
MAKE_LOG_FUNCTION(logSVC, svcLogger)
MAKE_LOG_FUNCTION(logThread, threadLogger)
MAKE_LOG_FUNCTION(logDebugString, debugStringLogger)
MAKE_LOG_FUNCTION(logError, errorLogger)
MAKE_LOG_FUNCTION(logFileIO, fileIOLogger)

View file

@ -18,26 +18,33 @@ namespace Log {
};
// Our loggers here. Enable/disable by toggling the template param
static Logger<true> kernelLogger;
static Logger<true> debugStringLogger; // Enables output for the outputDebugString SVC
static Logger<true> errorLogger;
static Logger<true> fileIOLogger;
static Logger<true> svcLogger;
static Logger<true> gpuLogger;
static Logger<false> kernelLogger;
static Logger<false> debugStringLogger; // Enables output for the outputDebugString SVC
static Logger<false> errorLogger;
static Logger<false> fileIOLogger;
static Logger<false> svcLogger;
static Logger<false> threadLogger;
static Logger<false> gpuLogger;
static Logger<false> rendererLogger;
// Service loggers
static Logger<true> aptLogger;
static Logger<true> cecdLogger;
static Logger<true> cfgLogger;
static Logger<true> dspServiceLogger;
static Logger<true> fsLogger;
static Logger<true> hidLogger;
static Logger<true> gspGPULogger;
static Logger<true> gspLCDLogger;
static Logger<true> micLogger;
static Logger<true> ndmLogger;
static Logger<true> ptmLogger;
static Logger<true> srvLogger;
static Logger<false> acLogger;
static Logger<false> amLogger;
static Logger<false> aptLogger;
static Logger<false> bossLogger;
static Logger<false> cecdLogger;
static Logger<false> cfgLogger;
static Logger<false> dspServiceLogger;
static Logger<false> frdLogger;
static Logger<false> fsLogger;
static Logger<false> hidLogger;
static Logger<false> gspGPULogger;
static Logger<false> gspLCDLogger;
static Logger<false> micLogger;
static Logger<false> nimLogger;
static Logger<false> ndmLogger;
static Logger<false> ptmLogger;
static Logger<false> srvLogger;
#define MAKE_LOG_FUNCTION(functionName, logger) \
template <typename... Args> \

View file

@ -156,6 +156,17 @@ public:
u32 getLinearHeapVaddr();
u8* getFCRAM() { return fcram; }
enum class BatteryLevel {
Empty = 0, AlmostEmpty, OneBar, TwoBars, ThreeBars, FourBars
};
u8 getBatteryState(bool adapterConnected, bool charging, BatteryLevel batteryLevel) {
u8 value = static_cast<u8>(batteryLevel) << 2; // Bits 2:4 are the battery level from 0 to 5
if (adapterConnected) value |= 1 << 0; // Bit 0 shows if the charger is connected
if (charging) value |= 1 << 1; // Bit 1 shows if we're charging
return value;
}
NCCH* getCXI() {
if (loadedCXI.has_value()) {
return &loadedCXI.value();

View file

@ -30,6 +30,9 @@
#include "gl3w.h"
// Uncomment the following define if you want GL objects to automatically free themselves when their lifetime ends
// #define OPENGL_DESTRUCTORS
namespace OpenGL {
// Workaround for using static_assert inside constexpr if
@ -51,7 +54,9 @@ namespace OpenGL {
}
}
~VertexArray() { glDeleteVertexArrays(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~VertexArray() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindVertexArray(m_handle); }
@ -121,6 +126,10 @@ namespace OpenGL {
void enableAttribute(GLuint index) { glEnableVertexAttribArray(index); }
void disableAttribute(GLuint index) { glDisableVertexAttribArray(index); }
void free() {
glDeleteVertexArrays(1, &m_handle);
}
};
enum FramebufferTypes {
@ -165,12 +174,16 @@ namespace OpenGL {
create(width, height, internalFormat, GL_TEXTURE_2D_MULTISAMPLE, samples);
}
~Texture() { glDeleteTextures(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~Texture() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindTexture(m_binding, m_handle); }
int width() { return m_width; }
int height() { return m_height; }
void free() { glDeleteTextures(1, &m_handle); }
};
struct Framebuffer {
@ -189,11 +202,14 @@ namespace OpenGL {
}
}
~Framebuffer() { glDeleteFramebuffers(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~Framebuffer() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind(GLenum target) { glBindFramebuffer(target, m_handle); }
void bind(FramebufferTypes target) { bind(static_cast<GLenum>(target)); }
void free() { glDeleteFramebuffers(1, &m_handle); }
void createWithTexture(Texture& tex, GLenum mode = GL_FRAMEBUFFER, GLenum textureType = GL_TEXTURE_2D) {
m_textureType = textureType;
@ -313,10 +329,13 @@ namespace OpenGL {
}
}
~VertexBuffer() { glDeleteBuffers(1, &m_handle); }
#ifdef OPENGL_DESTRUCTORS
~VertexBuffer() { free(); }
#endif
GLuint handle() { return m_handle; }
bool exists() { return m_handle != 0; }
void bind() { glBindBuffer(GL_ARRAY_BUFFER, m_handle); }
void free() { glDeleteBuffers(1, &m_handle); }
// Reallocates the buffer on every call. Prefer the sub version if possible.
template <typename VertType>

View file

@ -0,0 +1,69 @@
#pragma once
#include <array>
#include "helpers.hpp"
#include "logger.hpp"
#include "opengl.hpp"
#include "surface_cache.hpp"
struct Vertex {
OpenGL::vec4 position;
OpenGL::vec4 colour;
};
class Renderer {
OpenGL::Program triangleProgram;
OpenGL::Program displayProgram;
OpenGL::VertexArray vao;
OpenGL::VertexBuffer vbo;
GLint alphaControlLoc = -1;
u32 oldAlphaControl = 0;
SurfaceCache<DepthBuffer, 10> depthBufferCache;
SurfaceCache<ColourBuffer, 10> colourBufferCache;
OpenGL::uvec2 fbSize; // The size of the framebuffer (ie both the colour and depth buffer)'
u32 colourBufferLoc; // Location in 3DS VRAM for the colour buffer
ColourBuffer::Formats colourBufferFormat; // Format of the colours stored in the colour buffer
// Same for the depth/stencil buffer
u32 depthBufferLoc;
DepthBuffer::Formats depthBufferFormat;
// Dummy VAO/VBO for blitting the final output
OpenGL::VertexArray dummyVAO;
OpenGL::VertexBuffer dummyVBO;
static constexpr u32 regNum = 0x300; // Number of internal PICA registers
const std::array<u32, regNum>& regs;
OpenGL::Framebuffer getColourFBO();
MAKE_LOG_FUNCTION(log, rendererLogger)
public:
Renderer(const std::array<u32, regNum>& internalRegs) : regs(internalRegs) {}
void reset();
void display(); // Display the 3DS screen contents to the window
void initGraphicsContext(); // Initialize graphics context
void getGraphicsContext(); // Set up graphics context for rendering
void clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control); // Clear a GPU buffer in VRAM
void drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count); // Draw the given vertices
void setFBSize(u32 width, u32 height) {
fbSize.x() = width;
fbSize.y() = height;
}
void setColourFormat(ColourBuffer::Formats format) { colourBufferFormat = format; }
void setColourFormat(u32 format) { colourBufferFormat = static_cast<ColourBuffer::Formats>(format); }
void setDepthFormat(DepthBuffer::Formats format) { depthBufferFormat = format; }
void setDepthFormat(u32 format) { depthBufferFormat = static_cast<DepthBuffer::Formats>(format); }
void setColourBufferLoc(u32 loc) { colourBufferLoc = loc; }
void setDepthBufferLoc(u32 loc) { depthBufferLoc = loc; }
static constexpr u32 vertexBufferSize = 0x1500;
};

View file

@ -0,0 +1,65 @@
#pragma once
#include <functional>
#include <optional>
#include "surfaces.hpp"
// Surface cache class that can fit "capacity" instances of the "SurfaceType" class of surfaces
// SurfaceType *must* have all of the following
// - An "allocate" function that allocates GL resources for the surfaces
// - A "free" function that frees up all resources the surface is taking up
// - A "matches" function that, when provided with a SurfaceType object reference
// Will tell us if the 2 surfaces match (Only as far as location in VRAM, format, dimensions, etc)
// Are concerned. We could overload the == operator, but that implies full equality
// Including equality of the allocated OpenGL resources, which we don't want
// - A "valid" member that tells us whether the function is still valid or not
template <typename SurfaceType, size_t capacity>
class SurfaceCache {
// Vanilla std::optional can't hold actual references
using OptionalRef = std::optional<std::reference_wrapper<SurfaceType>>;
static_assert(std::is_same<SurfaceType, ColourBuffer>() || std::is_same<SurfaceType, DepthBuffer>(),
"Invalid surface type");
size_t size;
std::array<SurfaceType, capacity> buffer;
public:
void reset() {
size = 0;
for (auto& e : buffer) { // Free the VRAM of all surfaces
e.free();
}
}
OptionalRef find(SurfaceType& other) {
for (auto& e : buffer) {
if (e.matches(other) && e.valid)
return e;
}
return std::nullopt;
}
// Adds a surface object to the cache and returns it
SurfaceType add(SurfaceType& surface) {
if (size >= capacity) {
Helpers::panic("Surface cache full! Add emptying!");
}
size++;
// Find an invalid entry in the cache and overwrite it with the new surface
for (auto& e : buffer) {
if (!e.valid) {
e = surface;
e.allocate();
return e;
}
}
// This should be unreachable but helps to panic anyways
Helpers::panic("Couldn't add surface to cache\n");
}
SurfaceType& operator[](size_t i) {
return buffer[i];
}
};

View file

@ -0,0 +1,152 @@
#pragma once
#include "boost/icl/interval.hpp"
#include "helpers.hpp"
#include "opengl.hpp"
template <typename T>
using Interval = boost::icl::right_open_interval<T>;
struct ColourBuffer {
enum class Formats : u32 {
RGBA8 = 0,
BGR8 = 1,
RGB5A1 = 2,
RGB565 = 3,
RGBA4 = 4,
Trash1 = 5, Trash2 = 6, Trash3 = 7 // Technically selectable, but their function is unknown
};
u32 location;
Formats format;
OpenGL::uvec2 size;
bool valid;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL resources allocated to buffer
OpenGL::Texture texture;
OpenGL::Framebuffer fbo;
ColourBuffer() : valid(false) {}
ColourBuffer(u32 loc, Formats format, u32 x, u32 y, bool valid = true)
: location(loc), format(format), size({x, y}), valid(valid) {
u64 endLoc = (u64)loc + sizeInBytes();
// Check if start and end are valid here
range = Interval<u32>(loc, (u32)endLoc);
}
void allocate() {
// Create texture for the FBO, setting up filters and the like
// Reading back the current texture is slow, but allocate calls should be and far between.
// If this becomes a bottleneck, we can fix it semi-easily
auto prevTexture = OpenGL::getTex2D();
texture.create(size.x(), size.y(), GL_RGBA8);
texture.bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, prevTexture);
//Helpers::panic("Creating FBO: %d, %d\n", size.x(), size.y());
fbo.createWithDrawTexture(texture);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
Helpers::panic("Incomplete framebuffer");
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
// TODO: This should not clear the framebuffer contents. It should load them from VRAM.
GLint oldViewport[4];
glGetIntegerv(GL_VIEWPORT, oldViewport);
OpenGL::setViewport(size.x(), size.y());
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
OpenGL::setViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
}
void free() {
valid = false;
if (texture.exists() || fbo.exists())
Helpers::panic("Make this buffer free itself");
}
bool matches(ColourBuffer& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
// Size occupied by each pixel in bytes
// All formats are 16BPP except for RGBA8 (32BPP) and BGR8 (24BPP)
size_t sizePerPixel() {
switch (format) {
case Formats::BGR8: return 3;
case Formats::RGBA8: return 4;
default: return 2;
}
}
size_t sizeInBytes() {
return (size_t)size.x() * (size_t)size.y() * sizePerPixel();
}
};
struct DepthBuffer {
enum class Formats : u32 {
Depth16 = 0,
Garbage = 1,
Depth24 = 2,
Depth24Stencil8 = 3
};
u32 location;
Formats format;
OpenGL::uvec2 size; // Implicitly set to the size of the framebuffer
bool valid;
// Range of VRAM taken up by buffer
Interval<u32> range;
// OpenGL texture used for storing depth/stencil
OpenGL::Texture texture;
DepthBuffer() : valid(false) {}
DepthBuffer(u32 loc, Formats format, u32 x, u32 y, bool valid = true) :
location(loc), format(format), size({x, y}), valid(valid) {}
bool hasStencil() {
return format == Formats::Depth24Stencil8;
}
void allocate() {
printf("Make this depth buffer allocate itself\n");
}
void free() {
valid = false;
printf("Make this depth buffer free itself\n");
}
bool matches(DepthBuffer& other) {
return location == other.location && format == other.format &&
size.x() == other.size.x() && size.y() == other.size.y();
}
// Size occupied by each pixel in bytes
size_t sizePerPixel() {
switch (format) {
case Formats::Depth16: return 2;
case Formats::Depth24: return 3;
case Formats::Depth24Stencil8: return 4;
default: return 1; // Invalid format
}
}
size_t sizeInBytes() {
return (size_t)size.x() * (size_t)size.y() * sizePerPixel();
}
};

19
include/services/ac.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class ACService {
Handle handle = KernelHandles::AC;
Memory& mem;
MAKE_LOG_FUNCTION(log, acLogger)
// Service commands
void setClientVersion(u32 messagePointer);
public:
ACService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

19
include/services/am.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class AMService {
Handle handle = KernelHandles::AM;
Memory& mem;
MAKE_LOG_FUNCTION(log, amLogger)
// Service commands
void listTitleInfo(u32 messagePointer);
public:
AMService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -27,14 +27,33 @@ class APTService {
void checkNew3DSApp(u32 messagePointer);
void enable(u32 messagePointer);
void initialize(u32 messagePointer);
void inquireNotification(u32 messagePointer);
void notifyToWait(u32 messagePointer);
void receiveParameter(u32 messagePointer);
void replySleepQuery(u32 messagePointer);
void setApplicationCpuTimeLimit(u32 messagePointer);
void setScreencapPostPermission(u32 messagePointer);
// Percentage of the syscore available to the application, between 5% and 89%
u32 cpuTimeLimit;
enum class NotificationType : u32 {
None = 0,
HomeButton1 = 1,
HomeButton2 = 2,
SleepQuery = 3,
SleepCanceledByOpen = 4,
SleepAccepted = 5,
SleepAwake = 6,
Shutdown = 7,
PowerButtonClick = 8,
PowerButtonClear = 9,
TrySleep = 10,
OrderToClose = 11
};
u32 screencapPostPermission;
public:
APTService(Memory& mem, Kernel& kernel) : mem(mem), kernel(kernel) {}
void reset();

19
include/services/boss.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class BOSSService {
Handle handle = KernelHandles::BOSS;
Memory& mem;
MAKE_LOG_FUNCTION(log, bossLogger)
// Service commands
void initializeSession(u32 messagePointer);
public:
BOSSService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -9,7 +9,7 @@ class CECDService {
Memory& mem;
MAKE_LOG_FUNCTION(log, cecdLogger)
// Service commands
// Service commands
public:
CECDService(Memory& mem) : mem(mem) {}

View file

@ -50,6 +50,7 @@ class DSPService {
// Service functions
void convertProcessAddressFromDspDram(u32 messagePointer); // Nice function name
void flushDataCache(u32 messagePointer);
void getHeadphoneStatus(u32 messagePointer);
void getSemaphoreHandle(u32 messagePointer);
void loadComponent(u32 messagePointer);

21
include/services/frd.hpp Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class FRDService {
Handle handle = KernelHandles::FRD;
Memory& mem;
MAKE_LOG_FUNCTION(log, frdLogger)
// Service commands
void getMyFriendKey(u32 messagePointer);
void getMyPresence(u32 messagePointer);
void setClientSDKVersion(u32 messagePointer);
public:
FRDService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -1,4 +1,5 @@
#pragma once
#include "fs/archive_ext_save_data.hpp"
#include "fs/archive_ncch.hpp"
#include "fs/archive_save_data.hpp"
#include "fs/archive_sdmc.hpp"
@ -20,6 +21,7 @@ class FSService {
// The different filesystem archives (Save data, RomFS+ExeFS, etc)
SelfNCCHArchive selfNcch;
SaveDataArchive saveData;
ExtSaveDataArchive sharedExtSaveData;
SDMCArchive sdmc;
ArchiveBase* getArchiveFromID(u32 id);
@ -31,6 +33,7 @@ class FSService {
void getPriority(u32 messagePointer);
void initialize(u32 messagePointer);
void initializeWithSdkVersion(u32 messagePointer);
void isSdmcDetected(u32 messagePointer);
void openArchive(u32 messagePointer);
void openFile(u32 messagePointer);
void openFileDirectly(u32 messagePointer);
@ -40,7 +43,12 @@ class FSService {
u32 priority;
public:
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), sdmc(mem), selfNcch(mem), kernel(kernel) {}
FSService(Memory& mem, Kernel& kernel) : mem(mem), saveData(mem), sharedExtSaveData(mem), sdmc(mem), selfNcch(mem),
kernel(kernel)
{
sharedExtSaveData.isShared = true; // Need to do this here because templates and virtual classes do not mix well
}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -37,6 +37,7 @@ class GPUService {
void setAxiConfigQoSMode(u32 messagePointer);
void setInternalPriorities(u32 messagePointer);
void setLCDForceBlack(u32 messagePointer);
void storeDataCache(u32 messagePointer);
void triggerCmdReqQueue(u32 messagePointer);
void writeHwRegs(u32 messagePointer);
void writeHwRegsWithMask(u32 messagePointer);

19
include/services/nim.hpp Normal file
View file

@ -0,0 +1,19 @@
#pragma once
#include "helpers.hpp"
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
class NIMService {
Handle handle = KernelHandles::NIM;
Memory& mem;
MAKE_LOG_FUNCTION(log, nimLogger)
// Service commands
void initialize(u32 messagePointer);
public:
NIMService(Memory& mem) : mem(mem) {}
void reset();
void handleSyncRequest(u32 messagePointer);
};

View file

@ -4,15 +4,20 @@
#include "kernel_types.hpp"
#include "logger.hpp"
#include "memory.hpp"
#include "services/ac.hpp"
#include "services/am.hpp"
#include "services/apt.hpp"
#include "services/boss.hpp"
#include "services/cecd.hpp"
#include "services/cfg.hpp"
#include "services/dsp.hpp"
#include "services/hid.hpp"
#include "services/frd.hpp"
#include "services/fs.hpp"
#include "services/gsp_gpu.hpp"
#include "services/gsp_lcd.hpp"
#include "services/mic.hpp"
#include "services/nim.hpp"
#include "services/ndm.hpp"
#include "services/ptm.hpp"
@ -28,15 +33,20 @@ class ServiceManager {
MAKE_LOG_FUNCTION(log, srvLogger)
ACService ac;
AMService am;
APTService apt;
BOSSService boss;
CECDService cecd;
CFGService cfg;
DSPService dsp;
HIDService hid;
FRDService frd;
FSService fs;
GPUService gsp_gpu;
LCDService gsp_lcd;
MICService mic;
NIMService nim;
NDMService ndm;
PTMService ptm;
@ -45,6 +55,7 @@ class ServiceManager {
void getServiceHandle(u32 messagePointer);
void receiveNotification(u32 messagePointer);
void registerClient(u32 messagePointer);
void subscribe(u32 messagePointer);
public:
ServiceManager(std::array<u32, 16>& regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel);

View file

@ -9,7 +9,7 @@ CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, *this) {
config.arch_version = Dynarmic::A32::ArchVersion::v6K;
config.callbacks = &env;
config.coprocessors[15] = cp15;
// config.define_unpredictable_behaviour = true;
config.define_unpredictable_behaviour = true;
config.global_monitor = &exclusiveMonitor;
config.processor_id = 0;
@ -18,7 +18,7 @@ CPU::CPU(Memory& mem, Kernel& kernel) : mem(mem), env(mem, kernel, *this) {
void CPU::reset() {
setCPSR(CPSR::UserMode);
setFPSCR(FPSCR::ThreadDefault);
setFPSCR(FPSCR::MainThreadDefault);
env.totalTicks = 0;
cp15->reset();
@ -26,6 +26,7 @@ void CPU::reset() {
jit->Reset();
jit->ClearCache();
jit->Regs().fill(0);
jit->ExtRegs().fill(0);
}
#endif // CPU_DYNARMIC

View file

@ -5,7 +5,7 @@
using namespace Floats;
GPU::GPU(Memory& mem) : mem(mem) {
GPU::GPU(Memory& mem) : mem(mem), renderer(regs) {
vram = new u8[vramSize];
}
@ -27,7 +27,7 @@ void GPU::reset() {
e.config2 = 0;
}
// TODO: Reset blending, texturing, etc here
renderer.reset();
}
void GPU::drawArrays(bool indexed) {
@ -49,12 +49,13 @@ void GPU::drawArrays() {
const u32 primType = (primConfig >> 8) & 3;
if (primType != 0 && primType != 1) Helpers::panic("[PICA] Tried to draw unimplemented shape %d\n", primType);
if (vertexCount > vertexBufferSize) Helpers::panic("[PICA] vertexCount > vertexBufferSize");
if (vertexCount > Renderer::vertexBufferSize) Helpers::panic("[PICA] vertexCount > vertexBufferSize");
if ((primType == 0 && vertexCount % 3) || (primType == 1 && vertexCount < 3)) {
Helpers::panic("Invalid vertex count for primitive. Type: %d, vert count: %d\n", primType, vertexCount);
}
Vertex vertices[vertexBufferSize];
Vertex vertices[Renderer::vertexBufferSize];
// Get the configuration for the index buffer, used only for indexed drawing
u32 indexBufferConfig = regs[PICAInternalRegs::IndexBufferConfig];
@ -157,7 +158,7 @@ void GPU::drawArrays() {
OpenGL::Triangle, OpenGL::TriangleStrip, OpenGL::TriangleFan, OpenGL::LineStrip
};
const auto shape = primTypes[primType];
drawVertices(shape, vertices, vertexCount);
renderer.drawVertices(shape, vertices, vertexCount);
}
void GPU::fireDMA(u32 dest, u32 source, u32 size) {

View file

@ -4,8 +4,13 @@
using namespace Floats;
u32 GPU::readReg(u32 address) {
log("Ignoring read from GPU register %08X\n", address);
return 0;
if (address >= 0x1EF01000 && address < 0x1EF01C00) { // Internal registers
const u32 index = (address - 0x1EF01000) / sizeof(u32);
return readInternalReg(index);
} else {
log("Ignoring read to external GPU register %08X.\n", address);
return 0;
}
}
void GPU::writeReg(u32 address, u32 value) {
@ -54,6 +59,31 @@ void GPU::writeInternalReg(u32 index, u32 value, u32 mask) {
fixedAttribMask = (value >> 16) & 0xfff; // Determines which vertex attributes are fixed for all vertices
break;
case ColourBufferLoc: {
u32 loc = (value & 0x0fffffff) << 3;
renderer.setColourBufferLoc(loc);
break;
};
case ColourBufferFormat: {
u32 format = (value >> 16) & 7;
renderer.setColourFormat(format);
break;
}
case DepthBufferLoc: {
u32 loc = (value & 0x0fffffff) << 3;
renderer.setDepthBufferLoc(loc);
break;
}
case FramebufferSize: {
const u32 width = value & 0x7ff;
const u32 height = ((value >> 12) & 0x3ff) + 1;
renderer.setFBSize(width, height);
break;
}
case VertexFloatUniformIndex:
shaderUnit.vs.setFloatUniformIndex(value);
break;

View file

@ -30,6 +30,7 @@ void PICAShader::run() {
case ShaderOpcodes::MOVA: mova(instruction); break;
case ShaderOpcodes::MUL: mul(instruction); break;
case ShaderOpcodes::NOP: break; // Do nothing
case ShaderOpcodes::RCP: rcp(instruction); break;
case ShaderOpcodes::RSQ: rsq(instruction); break;
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
@ -292,6 +293,26 @@ void PICAShader::dp4(u32 instruction) {
}
}
void PICAShader::rcp(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;
const u32 idx = (instruction >> 19) & 3;
const u32 dest = (instruction >> 21) & 0x1f;
if (idx) Helpers::panic("[PICA] RCP: idx != 0");
vec4f srcVec1 = getSourceSwizzled<1>(src1, operandDescriptor);
vec4f& destVector = getDest(dest);
f24 res = f24::fromFloat32(1.0f) / srcVec1[0];
u32 componentMask = operandDescriptor & 0xf;
for (int i = 0; i < 4; i++) {
if (componentMask & (1 << i)) {
destVector[3 - i] = res;
}
}
}
void PICAShader::rsq(u32 instruction) {
const u32 operandDescriptor = operandDescriptors[instruction & 0x7f];
const u32 src1 = (instruction >> 12) & 0x7f;

View file

@ -0,0 +1,29 @@
#include "fs/archive_ext_save_data.hpp"
#include <memory>
bool ExtSaveDataArchive::openFile(const FSPath& path) {
if (path.type != PathType::Binary) {
Helpers::panic("ExtSaveData accessed with a non-binary path in OpenFile. Type: %d", path.type);
}
Helpers::panic("ExtSaveDataArchive::OpenFile: Failed");
return false;
}
ArchiveBase* ExtSaveDataArchive::openArchive(const FSPath& path) {
if (path.type != PathType::Binary || path.size != 12) {
Helpers::panic("ExtSaveData accessed with an invalid path in OpenArchive");
}
u32 mediaType = mem.read32(path.pointer);
u64 saveID = mem.read64(path.pointer + 4);
Helpers::panic("ExtSaveData: media type = %d\n", mediaType);
return this;
}
std::optional<u32> ExtSaveDataArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) {
Helpers::panic("ExtSaveDataArchive::ReadFile: Failed");
return std::nullopt;
}

View file

@ -37,7 +37,7 @@ void Kernel::clearEvent() {
logSVC("ClearEvent(event handle = %X)\n", handle);
if (event == nullptr) [[unlikely]] {
Helpers::panic("Tried to clear non-existent event");
Helpers::panic("Tried to clear non-existent event (handle = %X)", handle);
regs[0] = SVCResult::BadHandle;
return;
}
@ -154,6 +154,7 @@ void Kernel::waitSynchronizationN() {
}
regs[0] = SVCResult::Success;
regs[1] = waitAll ? handleCount - 1 : 0; // Index of the handle that triggered the exit. STUBBED
t.status = ThreadStatus::WaitSyncAll;
t.waitAll = waitAll;
t.outPointer = outPointer;

View file

@ -11,7 +11,7 @@ void Kernel::switchThread(int newThreadIndex) {
auto& oldThread = threads[currentThreadIndex];
auto& newThread = threads[newThreadIndex];
newThread.status = ThreadStatus::Running;
printf("Switching from thread %d to %d\n", currentThreadIndex, newThreadIndex);
logThread("Switching from thread %d to %d\n", currentThreadIndex, newThreadIndex);
// Bail early if the new thread is actually the old thread
if (currentThreadIndex == newThreadIndex) [[unlikely]] {
@ -70,8 +70,6 @@ std::optional<int> Kernel::getNextThread() {
if (canThreadRun(t)) {
return index;
}
// TODO: Check timeouts here
}
// No thread was found
@ -274,7 +272,7 @@ void Kernel::setThreadPriority() {
object->getData<Thread>()->priority = priority;
}
}
sortThreads();
rescheduleThreads();
}

View file

@ -74,10 +74,12 @@ u8 Memory::read8(u32 vaddr) {
}
else {
switch (vaddr) {
case ConfigMem::BatteryState: return getBatteryState(true, true, BatteryLevel::FourBars);
case ConfigMem::EnvInfo: return envInfo;
case ConfigMem::KernelVersionMinor: return u8(kernelVersion & 0xff);
case ConfigMem::KernelVersionMajor: return u8(kernelVersion >> 8);
case ConfigMem::LedState3D: return 1; // Report the 3D LED as always off (non-zero) for now
case ConfigMem::HeadphonesConnectedMaybe: return 0;
default: Helpers::panic("Unimplemented 8-bit read, addr: %08X", vaddr);
}
}
@ -115,6 +117,7 @@ u32 Memory::read32(u32 vaddr) {
return 0; // Set to 0 by PTM
case ConfigMem::AppMemAlloc: return appResourceLimits.maxCommit;
case ConfigMem::SyscoreVer: return 2;
case 0x1FF81000: return 0; // TODO: Figure out what this config mem address does
default:
if (vaddr >= VirtualAddrs::VramStart && vaddr < VirtualAddrs::VramStart + VirtualAddrs::VramSize) {

View file

@ -1,7 +1,6 @@
#include "renderer_gl/renderer_gl.hpp"
#include "PICA/float_types.hpp"
#include "PICA/gpu.hpp"
#include "PICA/regs.hpp"
#include "opengl.hpp"
using namespace Floats;
@ -38,7 +37,7 @@ const char* fragmentShader = R"(
float alpha = fragColour.a;
switch (func) {
case 0: discard; break; // Never pass alpha test
case 0: discard; // Never pass alpha test
case 1: break; // Always pass alpha test
case 2: // Pass if equal
if (alpha != reference)
@ -106,31 +105,19 @@ const char* displayFragmentShader = R"(
}
)";
void GPU::initGraphicsContext() {
// Set up texture for top screen
fboTexture.create(400, 240, GL_RGBA8);
fboTexture.bind();
void Renderer::reset() {
depthBufferCache.reset();
colourBufferCache.reset();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
// Init the colour/depth buffer settings to some random defaults on reset
colourBufferLoc = 0;
colourBufferFormat = ColourBuffer::Formats::RGBA8;
fbo.createWithDrawTexture(fboTexture);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
GLuint rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 400, 240); // use a single renderbuffer object for both a depth AND stencil buffer.
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
Helpers::panic("Incomplete framebuffer");
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
OpenGL::setViewport(400, 240);
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0);
OpenGL::clearColor();
depthBufferLoc = 0;
depthBufferFormat = DepthBuffer::Formats::Depth16;
}
void Renderer::initGraphicsContext() {
OpenGL::Shader vert(vertexShader, OpenGL::Vertex);
OpenGL::Shader frag(fragmentShader, OpenGL::Fragment);
triangleProgram.create({ vert, frag });
@ -157,19 +144,19 @@ void GPU::initGraphicsContext() {
dummyVBO.create();
dummyVAO.create();
reset();
}
void GPU::getGraphicsContext() {
void Renderer::getGraphicsContext() {
OpenGL::disableScissor();
OpenGL::setViewport(400, 240);
fbo.bind(OpenGL::DrawAndReadFramebuffer);
vbo.bind();
vao.bind();
triangleProgram.use();
}
void GPU::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) {
void Renderer::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count) {
// Adjust alpha test if necessary
const u32 alphaControl = regs[PICAInternalRegs::AlphaTestConfig];
if (alphaControl != oldAlphaControl) {
@ -177,6 +164,9 @@ void GPU::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count)
glUniform1ui(alphaControlLoc, alphaControl);
}
OpenGL::Framebuffer poop = getColourFBO();
poop.bind(OpenGL::DrawAndReadFramebuffer);
const u32 depthControl = regs[PICAInternalRegs::DepthAndColorMask];
bool depthEnable = depthControl & 1;
bool depthWriteEnable = (depthControl >> 12) & 1;
@ -191,8 +181,8 @@ void GPU::drawVertices(OpenGL::Primitives primType, Vertex* vertices, u32 count)
f24 depthOffset = f24::fromRaw(regs[PICAInternalRegs::DepthOffset] & 0xffffff);
printf("Depth enable: %d, func: %d, writeEnable: %d\n", depthEnable, depthFunc, depthWriteEnable);
if (depthScale.toFloat32() != -1.0 || depthOffset.toFloat32() != 0.0)
Helpers::panic("TODO: Implement depth scale/offset. Remove the depth *= -1.0 from vertex shader");
//if (depthScale.toFloat32() != -1.0 || depthOffset.toFloat32() != 0.0)
// Helpers::panic("TODO: Implement depth scale/offset. Remove the depth *= -1.0 from vertex shader");
// TODO: Actually use this
float viewportWidth = f24::fromRaw(regs[PICAInternalRegs::ViewportWidth] & 0xffffff).toFloat32() * 2.0;
@ -220,21 +210,24 @@ constexpr u32 topScreenBuffer = 0x1f000000;
constexpr u32 bottomScreenBuffer = 0x1f05dc00;
// Quick hack to display top screen for now
void GPU::display() {
void Renderer::display() {
OpenGL::disableDepth();
OpenGL::disableScissor();
OpenGL::bindScreenFramebuffer();
fboTexture.bind();
colourBufferCache[0].texture.bind();
displayProgram.use();
dummyVAO.bind();
OpenGL::setClearColor(0.0, 0.0, 0.0, 1.0); // Clear screen colour
OpenGL::setClearColor(0.0, 0.0, 1.0, 1.0); // Clear screen colour
OpenGL::clearColor();
OpenGL::setViewport(0, 240, 400, 240); // Actually draw our 3DS screen
OpenGL::draw(OpenGL::TriangleStrip, 4);
}
void GPU::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
void Renderer::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) {
return;
log("GPU: Clear buffer\nStart: %08X End: %08X\nValue: %08X Control: %08X\n", startAddress, endAddress, value, control);
const float r = float((value >> 24) & 0xff) / 255.0;
@ -253,4 +246,17 @@ void GPU::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control)
OpenGL::setClearColor(r, g, b, a);
OpenGL::clearColor();
}
OpenGL::Framebuffer Renderer::getColourFBO() {
//We construct a colour buffer object and see if our cache has any matching colour buffers in it
// If not, we allocate a texture & FBO for our framebuffer and store it in the cache
ColourBuffer sampleBuffer(colourBufferLoc, colourBufferFormat, fbSize.x(), fbSize.y());
auto buffer = colourBufferCache.find(sampleBuffer);
if (buffer.has_value()) {
return buffer.value().get().fbo;
} else {
return colourBufferCache.add(sampleBuffer).fbo;
}
}

30
src/core/services/ac.cpp Normal file
View file

@ -0,0 +1,30 @@
#include "services/ac.hpp"
namespace ACCommands {
enum : u32 {
SetClientVersion = 0x00400042
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void ACService::reset() {}
void ACService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case ACCommands::SetClientVersion: setClientVersion(messagePointer); break;
default: Helpers::panic("AC service requested. Command: %08X\n", command);
}
}
void ACService::setClientVersion(u32 messagePointer) {
u32 version = mem.read32(messagePointer + 4);
log("AC::SetClientVersion (version = %d)\n", version);
mem.write32(messagePointer + 4, Result::Success);
}

43
src/core/services/am.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "services/am.hpp"
namespace AMCommands {
enum : u32 {
ListTitleInfo = 0x10070102
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void AMService::reset() {}
void AMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case AMCommands::ListTitleInfo: listTitleInfo(messagePointer); break;
default: Helpers::panic("AM service requested. Command: %08X\n", command);
}
}
void AMService::listTitleInfo(u32 messagePointer) {
log("AM::ListDLCOrLicenseTicketInfos\n"); // Yes this is the actual name
u32 ticketCount = mem.read32(messagePointer + 4);
u64 titleID = mem.read64(messagePointer + 8);
u32 pointer = mem.read32(messagePointer + 24);
for (u32 i = 0; i < ticketCount; i++) {
mem.write64(pointer, titleID); // Title ID
mem.write64(pointer + 8, 0); // Ticket ID
mem.write16(pointer + 16, 0); // Version
mem.write16(pointer + 18, 0); // Padding
mem.write32(pointer + 20, 0); // Size
pointer += 24; // = sizeof(TicketInfo)
}
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, ticketCount);
}

View file

@ -6,12 +6,14 @@ namespace APTCommands {
GetLockHandle = 0x00010040,
Initialize = 0x00020080,
Enable = 0x00030040,
InquireNotification = 0x000B0040,
ReceiveParameter = 0x000D0080,
ReplySleepQuery = 0x003E0080,
NotifyToWait = 0x00430040,
AppletUtility = 0x004B00C2,
SetApplicationCpuTimeLimit = 0x004F0080,
GetApplicationCpuTimeLimit = 0x00500040,
SetScreencapPostPermission = 0x00550040,
CheckNew3DSApp = 0x01010000,
CheckNew3DS = 0x01020000
};
@ -49,12 +51,14 @@ void APTService::handleSyncRequest(u32 messagePointer) {
case APTCommands::CheckNew3DSApp: checkNew3DSApp(messagePointer); break;
case APTCommands::Enable: enable(messagePointer); break;
case APTCommands::Initialize: initialize(messagePointer); break;
case APTCommands::InquireNotification: inquireNotification(messagePointer); break;
case APTCommands::GetApplicationCpuTimeLimit: getApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::GetLockHandle: getLockHandle(messagePointer); break;
case APTCommands::NotifyToWait: notifyToWait(messagePointer); break;
case APTCommands::ReceiveParameter: receiveParameter(messagePointer); break;
case APTCommands::ReplySleepQuery: replySleepQuery(messagePointer); break;
case APTCommands::SetApplicationCpuTimeLimit: setApplicationCpuTimeLimit(messagePointer); break;
case APTCommands::SetScreencapPostPermission: setScreencapPostPermission(messagePointer); break;
default: Helpers::panic("APT service requested. Command: %08X\n", command);
}
}
@ -96,7 +100,17 @@ void APTService::initialize(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0x04000000); // Translation descriptor
mem.write32(messagePointer + 12, notificationEvent.value()); // Notification Event Handle
mem.write32(messagePointer + 12, resumeEvent.value()); // Resume Event Handle
mem.write32(messagePointer + 16, resumeEvent.value()); // Resume Event Handle
}
void APTService::inquireNotification(u32 messagePointer) {
log("APT::InquireNotification (STUBBED TO FAIL)\n");
// Thanks to our silly WaitSynchronization hacks, sometimes games will switch to the APT thread without actually getting a notif
// After REing the APT code, I figured that making InquireNotification fail is one way of making games not crash when this happens
// We should fix this in the future, when the sync object implementation is less hacky.
mem.write32(messagePointer + 4, Result::Failure);
mem.write32(messagePointer + 8, static_cast<u32>(NotificationType::None));
}
void APTService::getLockHandle(u32 messagePointer) {
@ -159,4 +173,13 @@ void APTService::getApplicationCpuTimeLimit(u32 messagePointer) {
log("APT::GetApplicationCpuTimeLimit\n");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, cpuTimeLimit);
}
void APTService::setScreencapPostPermission(u32 messagePointer) {
u32 perm = mem.read32(messagePointer + 4);
log("APT::SetScreencapPostPermission (perm = %d)\n");
// Apparently only 1-3 are valid values, but I see 0 used in some games like Pokemon Rumble
mem.write32(messagePointer, Result::Success);
screencapPostPermission = perm;
}

View file

@ -0,0 +1,28 @@
#include "services/boss.hpp"
namespace BOSSCommands {
enum : u32 {
InitializeSession = 0x00010082
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void BOSSService::reset() {}
void BOSSService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case BOSSCommands::InitializeSession: initializeSession(messagePointer); break;
default: Helpers::panic("BOSS service requested. Command: %08X\n", command);
}
}
void BOSSService::initializeSession(u32 messagePointer) {
log("BOSS::InitializeSession (stubbed)\n");
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -37,7 +37,12 @@ void CFGService::getConfigInfoBlk2(u32 messagePointer) {
mem.write8(output, static_cast<u8>(DSPService::SoundOutputMode::Stereo));
} else if (size == 1 && blockID == 0xA0002){ // System language
mem.write8(output, static_cast<u8>(LanguageCodes::English));
} else if (size == 0x20 && blockID == 0x50005) {
} else if (size == 4 && blockID == 0xB0000) { // Country info
mem.write8(output, 0); // Unknown
mem.write8(output + 1, 0); // Unknown
mem.write8(output + 2, 2); // Province (Temporarily stubbed to Washington DC like Citra)
mem.write8(output + 3, 49); // Country code (Temporarily stubbed to USA like Citra)
}else if (size == 0x20 && blockID == 0x50005) {
printf("[Unimplemented] Read stereo display settings from NAND\n");
} else {
Helpers::panic("Unhandled GetConfigInfoBlk2 configuration");

View file

@ -7,6 +7,7 @@ namespace DSPCommands {
WriteProcessPipe = 0x000D0082,
ReadPipeIfPossible = 0x001000C0,
LoadComponent = 0x001100C2,
FlushDataCache = 0x00130082,
RegisterInterruptEvents = 0x00150082,
GetSemaphoreHandle = 0x00160000,
SetSemaphoreMask = 0x00170040,
@ -30,6 +31,7 @@ void DSPService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case DSPCommands::ConvertProcessAddressFromDspDram: convertProcessAddressFromDspDram(messagePointer); break;
case DSPCommands::FlushDataCache: flushDataCache(messagePointer); break;
case DSPCommands::GetHeadphoneStatus: getHeadphoneStatus(messagePointer); break;
case DSPCommands::GetSemaphoreHandle: getSemaphoreHandle(messagePointer); break;
case DSPCommands::LoadComponent: loadComponent(messagePointer); break;
@ -133,4 +135,13 @@ void DSPService::writeProcessPipe(u32 messagePointer) {
log("DSP::writeProcessPipe (channel = %d, size = %X, buffer = %08X)\n", channel, size, buffer);
mem.write32(messagePointer + 4, Result::Success);
}
void DSPService::flushDataCache(u32 messagePointer) {
u32 address = mem.read32(messagePointer + 4);
u32 size = mem.read32(messagePointer + 8);
u32 process = mem.read32(messagePointer + 16);
log("DSP::FlushDataCache (addr = %08X, size = %08X, process = %X)\n", address, size, process);
mem.write32(messagePointer + 4, Result::Success);
}

56
src/core/services/frd.cpp Normal file
View file

@ -0,0 +1,56 @@
#include "services/frd.hpp"
namespace FRDCommands {
enum : u32 {
SetClientSdkVersion = 0x00320042,
GetMyFriendKey = 0x00050000,
GetMyPresence = 0x00080000
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void FRDService::reset() {}
void FRDService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case FRDCommands::GetMyFriendKey: getMyFriendKey(messagePointer); break;
case FRDCommands::GetMyPresence: getMyPresence(messagePointer); break;
case FRDCommands::SetClientSdkVersion: setClientSDKVersion(messagePointer); break;
default: Helpers::panic("FRD service requested. Command: %08X\n", command);
}
}
void FRDService::getMyFriendKey(u32 messagePointer) {
log("FRD::GetMyFriendKey");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Principal ID
mem.write32(messagePointer + 12, 0); // Padding (?)
mem.write32(messagePointer + 16, 0); // Local friend code
mem.write32(messagePointer + 20, 0);
}
void FRDService::getMyPresence(u32 messagePointer) {
static constexpr u32 presenceSize = 0x12C; // A presence seems to be 12C bytes of data, not sure what it contains
log("FRD::GetMyPresence\n");
u32 buffer = mem.read32(messagePointer + 0x104); // Buffer to write presence info to.
for (u32 i = 0; i < presenceSize; i += 4) { // Clear presence info with 0s for now
mem.write32(buffer + i, 0);
}
mem.write32(messagePointer + 4, Result::Success);
}
void FRDService::setClientSDKVersion(u32 messagePointer) {
u32 version = mem.read32(messagePointer + 4);
log("FRD::SetClientSdkVersion (version = %d)\n", version);
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -8,6 +8,7 @@ namespace FSCommands {
OpenFileDirectly = 0x08030204,
OpenArchive = 0x080C00C2,
CloseArchive = 0x080E0080,
IsSdmcDetected = 0x08170000,
InitializeWithSdkVersion = 0x08610042,
SetPriority = 0x08620040,
GetPriority = 0x08630000
@ -29,6 +30,7 @@ ArchiveBase* FSService::getArchiveFromID(u32 id) {
switch (id) {
case ArchiveID::SelfNCCH: return &selfNcch;
case ArchiveID::SaveData: return &saveData;
case ArchiveID::SharedExtSaveData: return &sharedExtSaveData;
case ArchiveID::SDMC: return &sdmc;
default:
Helpers::panic("Unknown archive. ID: %d\n", id);
@ -77,6 +79,7 @@ void FSService::handleSyncRequest(u32 messagePointer) {
case FSCommands::GetPriority: getPriority(messagePointer); break;
case FSCommands::Initialize: initialize(messagePointer); break;
case FSCommands::InitializeWithSdkVersion: initializeWithSdkVersion(messagePointer); break;
case FSCommands::IsSdmcDetected: isSdmcDetected(messagePointer); break;
case FSCommands::OpenArchive: openArchive(messagePointer); break;
case FSCommands::OpenFile: openFile(messagePointer); break;
case FSCommands::OpenFileDirectly: openFileDirectly(messagePointer); break;
@ -210,4 +213,10 @@ void FSService::setPriority(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
priority = value;
}
void FSService::isSdmcDetected(u32 messagePointer) {
log("FS::IsSdmcDetected\n");
mem.write32(messagePointer + 4, Result::Success);
mem.write32(messagePointer + 8, 0); // Whether SD is detected. For now we emulate a 3DS without an SD.
}

View file

@ -11,7 +11,8 @@ namespace ServiceCommands {
FlushDataCache = 0x00080082,
SetLCDForceBlack = 0x000B0040,
TriggerCmdReqQueue = 0x000C0000,
SetInternalPriorities = 0x001E0080
SetInternalPriorities = 0x001E0080,
StoreDataCache = 0x001F0082
};
}
@ -47,6 +48,7 @@ void GPUService::handleSyncRequest(u32 messagePointer) {
case ServiceCommands::SetAxiConfigQoSMode: setAxiConfigQoSMode(messagePointer); break;
case ServiceCommands::SetInternalPriorities: setInternalPriorities(messagePointer); break;
case ServiceCommands::SetLCDForceBlack: setLCDForceBlack(messagePointer); break;
case ServiceCommands::StoreDataCache: storeDataCache(messagePointer); break;
case ServiceCommands::TriggerCmdReqQueue: [[likely]] triggerCmdReqQueue(messagePointer); break;
case ServiceCommands::WriteHwRegs: writeHwRegs(messagePointer); break;
case ServiceCommands::WriteHwRegsWithMask: writeHwRegsWithMask(messagePointer); break;
@ -184,6 +186,15 @@ void GPUService::flushDataCache(u32 messagePointer) {
mem.write32(messagePointer + 4, Result::Success);
}
void GPUService::storeDataCache(u32 messagePointer) {
u32 address = mem.read32(messagePointer + 4);
u32 size = mem.read32(messagePointer + 8);
u32 processHandle = handle = mem.read32(messagePointer + 16);
log("GSP::GPU::StoreDataCache(address = %08X, size = %X, process = %X\n", address, size, processHandle);
mem.write32(messagePointer + 4, Result::Success);
}
void GPUService::setLCDForceBlack(u32 messagePointer) {
u32 flag = mem.read32(messagePointer + 4);
log("GSP::GPU::SetLCDForceBlank(flag = %d)\n", flag);

View file

@ -1,4 +1,5 @@
#include "services/hid.hpp"
#include <bit>
namespace HIDCommands {
enum : u32 {
@ -49,19 +50,25 @@ void HIDService::enableGyroscopeLow(u32 messagePointer) {
void HIDService::getGyroscopeLowCalibrateParam(u32 messagePointer) {
log("HID::GetGyroscopeLowCalibrateParam\n");
constexpr s16 unit = 6700; // Approximately from Citra which took it from hardware
mem.write32(messagePointer + 4, Result::Success);
// Fill calibration data with 0s since we don't have gyro
for (int i = 0; i < 5; i++) {
mem.write32(messagePointer + 8 + i * 4, 0);
// Fill calibration data (for x/y/z depending on i)
for (int i = 0; i < 3; i++) {
const u32 pointer = messagePointer + 8 + i * 3 * sizeof(u16); // Pointer to write the calibration info for the current coordinate
mem.write16(pointer, 0); // Zero point
mem.write16(pointer + 1 * sizeof(u16), unit); // Positive unit point
mem.write16(pointer + 2 * sizeof(u16), -unit); // Negative unit point
}
}
void HIDService::getGyroscopeCoefficient(u32 messagePointer) {
log("HID::GetGyroscopeLowRawToDpsCoefficient\n");
constexpr float gyroscopeCoeff = 14.375f; // Same as retail 3DS
mem.write32(messagePointer + 4, Result::Success);
// This should write a coeff value here but we don't have gyro so
mem.write32(messagePointer + 8, std::bit_cast<u32, float>(gyroscopeCoeff));
}
void HIDService::getIPCHandles(u32 messagePointer) {

28
src/core/services/nim.cpp Normal file
View file

@ -0,0 +1,28 @@
#include "services/nim.hpp"
namespace NIMCommands {
enum : u32 {
Initialize = 0x00210000
};
}
namespace Result {
enum : u32 {
Success = 0,
};
}
void NIMService::reset() {}
void NIMService::handleSyncRequest(u32 messagePointer) {
const u32 command = mem.read32(messagePointer);
switch (command) {
case NIMCommands::Initialize: initialize(messagePointer); break;
default: Helpers::panic("NIM service requested. Command: %08X\n", command);
}
}
void NIMService::initialize(u32 messagePointer) {
log("NIM::Initialize\n");
mem.write32(messagePointer + 4, Result::Success);
}

View file

@ -2,21 +2,28 @@
#include "kernel.hpp"
ServiceManager::ServiceManager(std::array<u32, 16>& regs, Memory& mem, GPU& gpu, u32& currentPID, Kernel& kernel)
: regs(regs), mem(mem), kernel(kernel), apt(mem, kernel), cecd(mem), cfg(mem), dsp(mem), hid(mem),
fs(mem, kernel), gsp_gpu(mem, gpu, currentPID), gsp_lcd(mem), mic(mem), ndm(mem), ptm(mem) {}
: regs(regs), mem(mem), kernel(kernel), ac(mem), am(mem), boss(mem), apt(mem, kernel), cecd(mem), cfg(mem),
dsp(mem), hid(mem), frd(mem), fs(mem, kernel), gsp_gpu(mem, gpu, currentPID), gsp_lcd(mem), mic(mem),
nim(mem), ndm(mem), ptm(mem) {}
static constexpr int MAX_NOTIFICATION_COUNT = 16;
// Reset every single service
void ServiceManager::reset() {
ac.reset();
am.reset();
apt.reset();
boss.reset();
cecd.reset();
cfg.reset();
dsp.reset();
hid.reset();
frd.reset();
fs.reset();
gsp_gpu.reset();
gsp_lcd.reset();
mic.reset();
nim.reset();
ndm.reset();
ptm.reset();
@ -60,6 +67,7 @@ void ServiceManager::handleSyncRequest(u32 messagePointer) {
case Commands::ReceiveNotification: receiveNotification(messagePointer); break;
case Commands::RegisterClient: registerClient(messagePointer); break;
case Commands::GetServiceHandle: getServiceHandle(messagePointer); break;
case Commands::Subscribe: subscribe(messagePointer); break;
default: Helpers::panic("Unknown \"srv:\" command: %08X", header);
}
}
@ -79,12 +87,18 @@ void ServiceManager::getServiceHandle(u32 messagePointer) {
std::string service = mem.readString(messagePointer + 4, 8);
log("srv::getServiceHandle (Service: %s, nameLength: %d, flags: %d)\n", service.c_str(), nameLength, flags);
if (service == "APT:S") { // TODO: APT:A, APT:S and APT:U are slightly different
if (service == "ac:u") {
handle = KernelHandles::AC;
} else if (service == "am:app") {
handle = KernelHandles::AM;
} else if (service == "APT:S") { // TODO: APT:A, APT:S and APT:U are slightly different
handle = KernelHandles::APT;
} else if (service == "APT:A") {
handle = KernelHandles::APT;
} else if (service == "APT:U") {
handle = KernelHandles::APT;
} else if (service == "boss:U") {
handle = KernelHandles::BOSS;
} else if (service == "cecd:u") {
handle = KernelHandles::CECD;
} else if (service == "cfg:u") {
@ -93,7 +107,9 @@ void ServiceManager::getServiceHandle(u32 messagePointer) {
handle = KernelHandles::DSP;
} else if (service == "hid:USER") {
handle = KernelHandles::HID;
} else if (service == "fs:USER") {
} else if (service == "frd:u") {
handle = KernelHandles::FRD;
} else if (service == "fs:USER") {
handle = KernelHandles::FS;
} else if (service == "gsp::Gpu") {
handle = KernelHandles::GPU;
@ -103,6 +119,8 @@ void ServiceManager::getServiceHandle(u32 messagePointer) {
handle = KernelHandles::MIC;
} else if (service == "ndm:u") {
handle = KernelHandles::NDM;
} else if (service == "nim:aoc") {
handle = KernelHandles::NIM;
} else if (service == "ptm:u") {
handle = KernelHandles::PTM;
} else {
@ -134,17 +152,29 @@ void ServiceManager::receiveNotification(u32 messagePointer) {
mem.write32(messagePointer + 8, 0); // Notification ID
}
void ServiceManager::subscribe(u32 messagePointer) {
u32 id = mem.read32(messagePointer + 4);
log("srv::Subscribe (id = %d) (stubbed)\n", id);
mem.write32(messagePointer + 4, Result::Success);
}
void ServiceManager::sendCommandToService(u32 messagePointer, Handle handle) {
switch (handle) {
case KernelHandles::AC: ac.handleSyncRequest(messagePointer); break;
case KernelHandles::AM: am.handleSyncRequest(messagePointer); break;
case KernelHandles::APT: apt.handleSyncRequest(messagePointer); break;
case KernelHandles::BOSS: boss.handleSyncRequest(messagePointer); break;
case KernelHandles::CECD: cecd.handleSyncRequest(messagePointer); break;
case KernelHandles::CFG: cfg.handleSyncRequest(messagePointer); break;
case KernelHandles::DSP: dsp.handleSyncRequest(messagePointer); break;
case KernelHandles::HID: hid.handleSyncRequest(messagePointer); break;
case KernelHandles::FRD: frd.handleSyncRequest(messagePointer); break;
case KernelHandles::FS: fs.handleSyncRequest(messagePointer); break;
case KernelHandles::GPU: [[likely]] gsp_gpu.handleSyncRequest(messagePointer); break;
case KernelHandles::LCD: gsp_lcd.handleSyncRequest(messagePointer); break;
case KernelHandles::MIC: mic.handleSyncRequest(messagePointer); break;
case KernelHandles::NIM: nim.handleSyncRequest(messagePointer); break;
case KernelHandles::NDM: ndm.handleSyncRequest(messagePointer); break;
case KernelHandles::PTM: ptm.handleSyncRequest(messagePointer); break;
default: Helpers::panic("Sent IPC message to unknown service %08X\n Command: %08X", handle, mem.read32(messagePointer));