From c7db6fe5dc39b6188e1eb7fbb61f0a0834d410c8 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:54:46 +0300 Subject: [PATCH 01/11] FIx DSP region calculation --- include/audio/hle_core.hpp | 2 +- src/core/audio/hle_core.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index b59dc811..117d9ecb 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -142,7 +142,7 @@ namespace Audio { } else if (counter1 == 0xffff && counter0 != 0xfffe) { return 0; } else { - return counter0 > counter1 ? 0 : 0; + return counter0 > counter1 ? 0 : 1; } } diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index d39bdbbf..23a99786 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -216,6 +216,11 @@ namespace Audio { SharedMemory& read = readRegion(); SharedMemory& write = writeRegion(); + // TODO: Properly implement mixers + // The DSP checks the DSP configuration dirty bits on every frame, applies them, and clears them + read.dspConfiguration.dirtyRaw = 0; + // read.dspConfiguration.dirtyRaw2 = 0; + for (int i = 0; i < sourceCount; i++) { // Update source configuration from the read region of shared memory auto& config = read.sourceConfigurations.config[i]; @@ -401,6 +406,7 @@ namespace Audio { // samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount); source.currentSamples.erase(source.currentSamples.begin(), source.currentSamples.begin() + sampleCount); + source.samplePosition += sampleCount; outputCount += sampleCount; } } From 45dd69d62ae144fdac5048cdc118c0d2749c484f Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:58:00 +0300 Subject: [PATCH 02/11] HLE DSP: Pop unused samples when loading new buffer --- src/core/audio/hle_core.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 23a99786..112db9d5 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -370,6 +371,13 @@ namespace Audio { break; } + // We're skipping the first samplePosition samples, so remove them from the buffer so as not to consume them later + if (source.samplePosition > 0) { + auto start = source.currentSamples.begin(); + auto end = std::next(start, source.samplePosition); + source.currentSamples.erase(start, end); + } + // If the buffer is a looping buffer, re-push it if (buffer.looping) { source.pushBuffer(buffer); From 57ecc18f325f09d629b5cf491b3f7844f927b4da Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:03:17 +0300 Subject: [PATCH 03/11] HLE DSP: Implement buffer queue dirty bit --- src/core/audio/hle_core.cpp | 46 +++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 112db9d5..c01a8ccd 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -220,7 +220,7 @@ namespace Audio { // TODO: Properly implement mixers // The DSP checks the DSP configuration dirty bits on every frame, applies them, and clears them read.dspConfiguration.dirtyRaw = 0; - // read.dspConfiguration.dirtyRaw2 = 0; + read.dspConfiguration.dirtyRaw2 = 0; for (int i = 0; i < sourceCount; i++) { // Update source configuration from the read region of shared memory @@ -322,8 +322,40 @@ namespace Audio { } if (config.bufferQueueDirty) { - config.bufferQueueDirty = 0; // printf("Buffer queue dirty for voice %d\n", source.index); + + u16 dirtyBuffers = config.buffersDirty; + config.bufferQueueDirty = 0; + config.buffersDirty = 0; + + for (int i = 0; i < 4; i++) { + bool dirty = ((dirtyBuffers >> i) & 1) != 0; + if (dirty) { + const auto& buffer = config.buffers[i]; + + if (s32(buffer.length) >= 0) [[likely]] { + // TODO: Add sample format and channel count + Source::Buffer newBuffer{ + .paddr = buffer.physicalAddress, + .sampleCount = buffer.length, + .adpcmScale = u8(buffer.adpcmScale), + .previousSamples = {s16(buffer.adpcm_yn[0]), s16(buffer.adpcm_yn[1])}, + .adpcmDirty = buffer.adpcmDirty != 0, + .looping = buffer.isLooping != 0, + .bufferID = buffer.bufferID, + .playPosition = 0, + .format = source.sampleFormat, + .sourceType = source.sourceType, + .fromQueue = true, + .hasPlayedOnce = false, + }; + + source.buffers.emplace(std::move(newBuffer)); + } else { + printf("Buffer queue dirty: Invalid buffer size for DSP voice %d\n", source.index); + } + } + } } config.dirtyRaw = 0; @@ -371,17 +403,17 @@ namespace Audio { break; } + // If the buffer is a looping buffer, re-push it + if (buffer.looping) { + source.pushBuffer(buffer); + } + // We're skipping the first samplePosition samples, so remove them from the buffer so as not to consume them later if (source.samplePosition > 0) { auto start = source.currentSamples.begin(); auto end = std::next(start, source.samplePosition); source.currentSamples.erase(start, end); } - - // If the buffer is a looping buffer, re-push it - if (buffer.looping) { - source.pushBuffer(buffer); - } } void HLE_DSP::generateFrame(DSPSource& source) { From 6668ba3e37fe4b2dce6840c818d0ae78524f2242 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:46:36 +0300 Subject: [PATCH 04/11] HLE DSP: Fix embedded buffer starting sample position --- src/core/audio/hle_core.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index c01a8ccd..f150482b 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -292,6 +292,9 @@ namespace Audio { } if (config.embeddedBufferDirty) { + // Annoyingly, and only for embedded buffer, whether we use config.playPosition depends on the relevant dirty bit + const u32 playPosition = config.playPositionDirty ? config.playPosition : 0; + config.embeddedBufferDirty = 0; if (s32(config.length) >= 0) [[likely]] { // TODO: Add sample format and channel count @@ -303,7 +306,7 @@ namespace Audio { .adpcmDirty = config.adpcmDirty != 0, .looping = config.isLooping != 0, .bufferID = config.bufferID, - .playPosition = config.playPosition, + .playPosition = playPosition, .format = source.sampleFormat, .sourceType = source.sourceType, .fromQueue = false, From e26f58595eb4667a07ef14fbeb41bc3a73590f58 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Tue, 30 Jul 2024 00:36:16 +0300 Subject: [PATCH 05/11] HLE DSP: Reset flags should take priority --- src/core/audio/hle_core.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index f150482b..84d62401 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -253,6 +253,17 @@ namespace Audio { return; } + // The reset flags take priority, as you can reset a source and set it up to be played again at the same time + if (config.resetFlag) { + config.resetFlag = 0; + source.reset(); + } + + if (config.partialResetFlag) { + config.partialResetFlag = 0; + source.buffers = {}; + } + if (config.enableDirty) { config.enableDirty = 0; source.enabled = config.enable != 0; @@ -272,16 +283,6 @@ namespace Audio { ); } - if (config.resetFlag) { - config.resetFlag = 0; - source.reset(); - } - - if (config.partialResetFlag) { - config.partialResetFlag = 0; - source.buffers = {}; - } - // TODO: Should we check bufferQueueDirty here too? if (config.formatDirty || config.embeddedBufferDirty) { source.sampleFormat = config.format; From e666afd1a30d80f69df1ea015ed835ac86af1eef Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 31 Jul 2024 02:51:40 +0300 Subject: [PATCH 06/11] DSP HLE: Fix buffer queue metadata --- include/audio/hle_core.hpp | 2 +- src/core/audio/hle_core.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index 117d9ecb..35c1c1b8 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -142,7 +142,7 @@ namespace Audio { } else if (counter1 == 0xffff && counter0 != 0xfffe) { return 0; } else { - return counter0 > counter1 ? 0 : 1; + return (counter0 > counter1) ? 0 : 1; } } diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index 84d62401..ffab9301 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -342,7 +342,7 @@ namespace Audio { Source::Buffer newBuffer{ .paddr = buffer.physicalAddress, .sampleCount = buffer.length, - .adpcmScale = u8(buffer.adpcmScale), + .adpcmScale = u8(buffer.adpcm_ps), .previousSamples = {s16(buffer.adpcm_yn[0]), s16(buffer.adpcm_yn[1])}, .adpcmDirty = buffer.adpcmDirty != 0, .looping = buffer.isLooping != 0, From 195f3388e9a5f88e18ee2779c71069c60668f544 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:53:51 +0300 Subject: [PATCH 07/11] PICA: Add LITP test + interpreter implementation --- include/PICA/shader.hpp | 3 +- src/core/PICA/shader_interpreter.cpp | 32 ++++ tests/PICA_LITP/Makefile | 255 ++++++++++++++++++++++++++ tests/PICA_LITP/source/main.c | 128 +++++++++++++ tests/PICA_LITP/source/vshader.v.pica | 73 ++++++++ 5 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 tests/PICA_LITP/Makefile create mode 100644 tests/PICA_LITP/source/main.c create mode 100644 tests/PICA_LITP/source/vshader.v.pica diff --git a/include/PICA/shader.hpp b/include/PICA/shader.hpp index 68b16de8..e5f57c72 100644 --- a/include/PICA/shader.hpp +++ b/include/PICA/shader.hpp @@ -23,7 +23,7 @@ namespace ShaderOpcodes { DST = 0x04, EX2 = 0x05, LG2 = 0x06, - LIT = 0x07, + LITP = 0x07, MUL = 0x08, SGE = 0x09, SLT = 0x0A, @@ -161,6 +161,7 @@ class PICAShader { void jmpc(u32 instruction); void jmpu(u32 instruction); void lg2(u32 instruction); + void litp(u32 instruction); void loop(u32 instruction); void mad(u32 instruction); void madi(u32 instruction); diff --git a/src/core/PICA/shader_interpreter.cpp b/src/core/PICA/shader_interpreter.cpp index 003ef97a..a85c7464 100644 --- a/src/core/PICA/shader_interpreter.cpp +++ b/src/core/PICA/shader_interpreter.cpp @@ -74,6 +74,9 @@ void PICAShader::run() { break; } + // Undocumented, implementation based on 3DBrew and hw testing (see tests/PICA_LITP) + case ShaderOpcodes::LITP: [[unlikely]] litp(instruction); break; + default: Helpers::panic("Unimplemented PICA instruction %08X (Opcode = %02X)", instruction, opcode); } @@ -753,4 +756,33 @@ void PICAShader::jmpu(u32 instruction) { if (((boolUniform >> bit) & 1) == test) // Jump if the bool uniform is the value we want pc = dest; +} + +void PICAShader::litp(u32 instruction) { + const u32 operandDescriptor = operandDescriptors[instruction & 0x7f]; + u32 src = getBits<12, 7>(instruction); + const u32 idx = getBits<19, 2>(instruction); + const u32 dest = getBits<21, 5>(instruction); + + src = getIndexedSource(src, idx); + vec4f srcVec = getSourceSwizzled<1>(src, operandDescriptor); + vec4f& destVector = getDest(dest); + + // Compare registers are set based on whether src.x and src.w are >= 0.0 + cmpRegister[0] = (srcVec[0].toFloat32() >= 0.0f); + cmpRegister[1] = (srcVec[3].toFloat32() >= 0.0f); + + vec4f result; + // TODO: Does max here have the same non-IEEE NaN behavior as the max instruction? + result[0] = f24::fromFloat32(std::max(srcVec[0].toFloat32(), 0.0f)); + result[1] = f24::fromFloat32(std::clamp(srcVec[1].toFloat32(), -127.9961f, 127.9961f)); + result[2] = f24::zero(); + result[3] = f24::fromFloat32(std::max(srcVec[3].toFloat32(), 0.0f)); + + u32 componentMask = operandDescriptor & 0xf; + for (int i = 0; i < 4; i++) { + if (componentMask & (1 << i)) { + destVector[3 - i] = result[3 - i]; + } + } } \ No newline at end of file diff --git a/tests/PICA_LITP/Makefile b/tests/PICA_LITP/Makefile new file mode 100644 index 00000000..46a94048 --- /dev/null +++ b/tests/PICA_LITP/Makefile @@ -0,0 +1,255 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# GRAPHICS is a list of directories containing graphics files +# GFXBUILD is the directory where converted graphics files will be placed +# If set to $(BUILD), it will statically link in the converted +# files as if they were data files. +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# ROMFS is the directory which contains the RomFS, relative to the Makefile (Optional) +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := data +INCLUDES := include +GRAPHICS := gfx +GFXBUILD := $(BUILD) +#ROMFS := romfs +#GFXBUILD := $(ROMFS)/gfx + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -ffunction-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -D__3DS__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcitro3d -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PICAFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.v.pica))) +SHLISTFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.shlist))) +GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.t3s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +#--------------------------------------------------------------------------------- +ifeq ($(GFXBUILD),$(BUILD)) +#--------------------------------------------------------------------------------- +export T3XFILES := $(GFXFILES:.t3s=.t3x) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- +export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES)) +export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES)) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ + $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ + $(addsuffix .o,$(T3XFILES)) + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(PICAFILES:.v.pica=_shbin.h) $(SHLISTFILES:.shlist=_shbin.h) \ + $(addsuffix .h,$(subst .,_,$(BINFILES))) \ + $(GFXFILES:.t3s=.h) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export _3DSXDEPS := $(if $(NO_SMDH),,$(OUTPUT).smdh) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +ifneq ($(ROMFS),) + export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) +endif + +.PHONY: all clean + +#--------------------------------------------------------------------------------- +all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +$(BUILD): + @mkdir -p $@ + +ifneq ($(GFXBUILD),$(BUILD)) +$(GFXBUILD): + @mkdir -p $@ +endif + +ifneq ($(DEPSDIR),$(BUILD)) +$(DEPSDIR): + @mkdir -p $@ +endif + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(GFXBUILD) + +#--------------------------------------------------------------------------------- +$(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).3dsx : $(OUTPUT).elf $(_3DSXDEPS) + +$(OFILES_SOURCES) : $(HFILES) + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +.PRECIOUS : %.t3x +#--------------------------------------------------------------------------------- +%.t3x.o %_t3x.h : %.t3x +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# rules for assembling GPU shaders +#--------------------------------------------------------------------------------- +define shader-as + $(eval CURBIN := $*.shbin) + $(eval DEPSFILE := $(DEPSDIR)/$*.shbin.d) + echo "$(CURBIN).o: $< $1" > $(DEPSFILE) + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h + echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h + echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h + picasso -o $(CURBIN) $1 + bin2s $(CURBIN) | $(AS) -o $*.shbin.o +endef + +%.shbin.o %_shbin.h : %.v.pica %.g.pica + @echo $(notdir $^) + @$(call shader-as,$^) + +%.shbin.o %_shbin.h : %.v.pica + @echo $(notdir $<) + @$(call shader-as,$<) + +%.shbin.o %_shbin.h : %.shlist + @echo $(notdir $<) + @$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)$(file))) + +#--------------------------------------------------------------------------------- +%.t3x %.h : %.t3s +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @tex3ds -i $< -H $*.h -d $*.d -o $*.t3x + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/tests/PICA_LITP/source/main.c b/tests/PICA_LITP/source/main.c new file mode 100644 index 00000000..3c2a3f0c --- /dev/null +++ b/tests/PICA_LITP/source/main.c @@ -0,0 +1,128 @@ +#include <3ds.h> +#include +#include +#include "vshader_shbin.h" + +#define CLEAR_COLOR 0x68B0D8FF + +#define DISPLAY_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | \ + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ + GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) + +static DVLB_s* vshader_dvlb; +static shaderProgram_s program; +static int uLoc_projection; +static C3D_Mtx projection; + +static void sceneInit(void) +{ + // Load the vertex shader, create a shader program and bind it + vshader_dvlb = DVLB_ParseFile((u32*)vshader_shbin, vshader_shbin_size); + shaderProgramInit(&program); + shaderProgramSetVsh(&program, &vshader_dvlb->DVLE[0]); + C3D_BindProgram(&program); + + // Get the location of the uniforms + uLoc_projection = shaderInstanceGetUniformLocation(program.vertexShader, "projection"); + + // Configure attributes for use with the vertex shader + // Attribute format and element count are ignored in immediate mode + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v1=color + + // Compute the projection matrix + Mtx_OrthoTilt(&projection, 0.0, 400.0, 0.0, 240.0, 0.0, 1.0, true); + + // Configure the first fragment shading substage to just pass through the vertex color + // See https://www.opengl.org/sdk/docs/man2/xhtml/glTexEnv.xml for more insight + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvInit(env); + C3D_TexEnvSrc(env, C3D_Both, GPU_PRIMARY_COLOR, 0, 0); + C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); +} + +static void sceneRender(void) +{ + // Update the uniforms + C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); + + // Draw the triangle directly + C3D_ImmDrawBegin(GPU_TRIANGLES); + // Triangle 1 + // This vertex has r >= 0 and a >= 0 so the shader should output magenta (cmp.x = cmp.y = 1) + C3D_ImmSendAttrib(200.0f, 200.0f, 0.5f, 0.0f); // v0=position + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); // v1=color + + // This vertex only has a >= 0, so the shader should output lime (cmp.x = 0, cmp.y = 1) + C3D_ImmSendAttrib(100.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(-0.5f, 1.0f, 0.0f, 1.0f); + + // This vertex only has r >= 0, so the shader should output cyan (cmp.x = 1, cmp.y = 0) + C3D_ImmSendAttrib(300.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.5f, 0.0f, 1.0f, -1.0f); + + // Triangle 2 + // The next 3 vertices have r < 0, a < 0, so the output of the shader should be the output of litp with alpha set to 1 (cmp.x = cmp.y = 0) + C3D_ImmSendAttrib(10.0f, 20.0f, 0.5f, 0.0f); + // Output g component should be 64 / 128 = 0.5 + C3D_ImmSendAttrib(-1.0f, 64.0f, 0.0f, -1.0f); + + C3D_ImmSendAttrib(90.0f, 20.0f, 0.5f, 0.0f); + // Output g component should be 128 / 128 = 1.0 + C3D_ImmSendAttrib(-1.0f, 256.0f, 1.0f, -1.0f); + + C3D_ImmSendAttrib(40.0f, 40.0f, 0.5f, 0.0f); + // Output g component should be 0 / 128 = 0 + C3D_ImmSendAttrib(-1.0f, 0.0f, 0.5f, -1.0f); + C3D_ImmDrawEnd(); +} + +static void sceneExit(void) +{ + // Free the shader program + shaderProgramFree(&program); + DVLB_Free(vshader_dvlb); +} + +int main() +{ + // Initialize graphics + gfxInitDefault(); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + + // Initialize the render target + C3D_RenderTarget* target = C3D_RenderTargetCreate(240, 400, GPU_RB_RGBA8, GPU_RB_DEPTH24_STENCIL8); + C3D_RenderTargetSetOutput(target, GFX_TOP, GFX_LEFT, DISPLAY_TRANSFER_FLAGS); + + // Initialize the scene + sceneInit(); + + // Main loop + while (aptMainLoop()) + { + hidScanInput(); + + // Respond to user input + u32 kDown = hidKeysDown(); + if (kDown & KEY_START) + break; // break in order to return to hbmenu + + // Render the scene + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C3D_RenderTargetClear(target, C3D_CLEAR_ALL, CLEAR_COLOR, 0); + C3D_FrameDrawOn(target); + sceneRender(); + C3D_FrameEnd(0); + } + + // Deinitialize the scene + sceneExit(); + + // Deinitialize graphics + C3D_Fini(); + gfxExit(); + return 0; +} \ No newline at end of file diff --git a/tests/PICA_LITP/source/vshader.v.pica b/tests/PICA_LITP/source/vshader.v.pica new file mode 100644 index 00000000..d745f939 --- /dev/null +++ b/tests/PICA_LITP/source/vshader.v.pica @@ -0,0 +1,73 @@ +; Example PICA200 vertex shader + +; Uniforms +.fvec projection[4] + +; Constants +.constf myconst(0.0, 1.0, -1.0, 0.1) +.constf myconst2(0.3, 0.0, 0.0, 0.0) +.alias zeros myconst.xxxx ; Vector full of zeros +.alias ones myconst.yyyy ; Vector full of ones + +.constf magenta(0.8, 0.192, 0.812, 1.0) +.constf cyan(0.137, 0.949, 0.906, 1.0) +.constf lime(0.286, 0.929, 0.412, 1.0) + +.constf normalize_y(1.0, 1.0/128.0, 1.0, 1.0) + +; Outputs +.out outpos position +.out outclr color + +; Inputs (defined as aliases for convenience) +.alias inpos v0 +.alias inclr v1 + +.bool test + +.proc main + ; Force the w component of inpos to be 1.0 + mov r0.xyz, inpos + mov r0.w, ones + + ; outpos = projectionMatrix * inpos + dp4 outpos.x, projection[0], r0 + dp4 outpos.y, projection[1], r0 + dp4 outpos.z, projection[2], r0 + dp4 outpos.w, projection[3], r0 + + ; Test litp via the output fragment colour + ; r1 = input colour + mov r1, inclr + + ; This should perform the following operation: + ; cmp = (x >= 0, w >= 0) + ; dest = ( max(x, 0), clamp(y, -128, +128 ), 0, max(w, 0) ); + litp r2, r1 + + ifc cmp.x + ifc cmp.y + ; cmp.x = 1, cmp.y = 1, write magenta + mov outclr, magenta + end + .else + ; cmp.x = 1, cmp.y = 0, write cyan + mov outclr, cyan + end + .end + .else + ifc cmp.y + ; cmp.x = 0, cmp.y + mov outclr, lime + end + .end + .end + + ; cmp.x 0, cmp.y = 0, write output of litp to out colour, with y normalized to [-1, 1] + mul r2.xyz, normalize_y, r2 + ; Set alpha to one + mov r2.a, ones.a + + mov outclr, r2 + end +.end \ No newline at end of file From 24c4e02143f2803cfbee7723bb6c480605911d4d Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:59:33 +0300 Subject: [PATCH 08/11] Format litp test --- tests/PICA_LITP/source/main.c | 79 ++++++++++++++++------------------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/tests/PICA_LITP/source/main.c b/tests/PICA_LITP/source/main.c index 3c2a3f0c..9bcab5b9 100644 --- a/tests/PICA_LITP/source/main.c +++ b/tests/PICA_LITP/source/main.c @@ -1,22 +1,22 @@ #include <3ds.h> #include #include + #include "vshader_shbin.h" + #define CLEAR_COLOR 0x68B0D8FF -#define DISPLAY_TRANSFER_FLAGS \ - (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | \ - GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | \ - GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) +#define DISPLAY_TRANSFER_FLAGS \ + (GX_TRANSFER_FLIP_VERT(0) | GX_TRANSFER_OUT_TILED(0) | GX_TRANSFER_RAW_COPY(0) | GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | \ + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_SCALING(GX_TRANSFER_SCALE_NO)) static DVLB_s* vshader_dvlb; static shaderProgram_s program; static int uLoc_projection; static C3D_Mtx projection; -static void sceneInit(void) -{ +static void sceneInit(void) { // Load the vertex shader, create a shader program and bind it vshader_dvlb = DVLB_ParseFile((u32*)vshader_shbin, vshader_shbin_size); shaderProgramInit(&program); @@ -30,8 +30,8 @@ static void sceneInit(void) // Attribute format and element count are ignored in immediate mode C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); AttrInfo_Init(attrInfo); - AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position - AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v1=color + AttrInfo_AddLoader(attrInfo, 0, GPU_FLOAT, 3); // v0=position + AttrInfo_AddLoader(attrInfo, 1, GPU_FLOAT, 3); // v1=color // Compute the projection matrix Mtx_OrthoTilt(&projection, 0.0, 400.0, 0.0, 240.0, 0.0, 1.0, true); @@ -44,51 +44,48 @@ static void sceneInit(void) C3D_TexEnvFunc(env, C3D_Both, GPU_REPLACE); } -static void sceneRender(void) -{ +static void sceneRender(void) { // Update the uniforms C3D_FVUnifMtx4x4(GPU_VERTEX_SHADER, uLoc_projection, &projection); // Draw the triangle directly C3D_ImmDrawBegin(GPU_TRIANGLES); - // Triangle 1 - // This vertex has r >= 0 and a >= 0 so the shader should output magenta (cmp.x = cmp.y = 1) - C3D_ImmSendAttrib(200.0f, 200.0f, 0.5f, 0.0f); // v0=position - C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); // v1=color + // Triangle 1 + // This vertex has r >= 0 and a >= 0 so the shader should output magenta (cmp.x = cmp.y = 1) + C3D_ImmSendAttrib(200.0f, 200.0f, 0.5f, 0.0f); // v0=position + C3D_ImmSendAttrib(1.0f, 0.0f, 0.0f, 1.0f); // v1=color - // This vertex only has a >= 0, so the shader should output lime (cmp.x = 0, cmp.y = 1) - C3D_ImmSendAttrib(100.0f, 40.0f, 0.5f, 0.0f); - C3D_ImmSendAttrib(-0.5f, 1.0f, 0.0f, 1.0f); + // This vertex only has a >= 0, so the shader should output lime (cmp.x = 0, cmp.y = 1) + C3D_ImmSendAttrib(100.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(-0.5f, 1.0f, 0.0f, 1.0f); - // This vertex only has r >= 0, so the shader should output cyan (cmp.x = 1, cmp.y = 0) - C3D_ImmSendAttrib(300.0f, 40.0f, 0.5f, 0.0f); - C3D_ImmSendAttrib(0.5f, 0.0f, 1.0f, -1.0f); + // This vertex only has r >= 0, so the shader should output cyan (cmp.x = 1, cmp.y = 0) + C3D_ImmSendAttrib(300.0f, 40.0f, 0.5f, 0.0f); + C3D_ImmSendAttrib(0.5f, 0.0f, 1.0f, -1.0f); - // Triangle 2 - // The next 3 vertices have r < 0, a < 0, so the output of the shader should be the output of litp with alpha set to 1 (cmp.x = cmp.y = 0) - C3D_ImmSendAttrib(10.0f, 20.0f, 0.5f, 0.0f); - // Output g component should be 64 / 128 = 0.5 - C3D_ImmSendAttrib(-1.0f, 64.0f, 0.0f, -1.0f); + // Triangle 2 + // The next 3 vertices have r < 0, a < 0, so the output of the shader should be the output of litp with alpha set to 1 (cmp.x = cmp.y = 0) + C3D_ImmSendAttrib(10.0f, 20.0f, 0.5f, 0.0f); + // Output g component should be 64 / 128 = 0.5 + C3D_ImmSendAttrib(-1.0f, 64.0f, 0.0f, -1.0f); - C3D_ImmSendAttrib(90.0f, 20.0f, 0.5f, 0.0f); - // Output g component should be 128 / 128 = 1.0 - C3D_ImmSendAttrib(-1.0f, 256.0f, 1.0f, -1.0f); + C3D_ImmSendAttrib(90.0f, 20.0f, 0.5f, 0.0f); + // Output g component should be 128 / 128 = 1.0 + C3D_ImmSendAttrib(-1.0f, 256.0f, 1.0f, -1.0f); - C3D_ImmSendAttrib(40.0f, 40.0f, 0.5f, 0.0f); - // Output g component should be 0 / 128 = 0 - C3D_ImmSendAttrib(-1.0f, 0.0f, 0.5f, -1.0f); + C3D_ImmSendAttrib(40.0f, 40.0f, 0.5f, 0.0f); + // Output g component should be 0 / 128 = 0 + C3D_ImmSendAttrib(-1.0f, 0.0f, 0.5f, -1.0f); C3D_ImmDrawEnd(); } -static void sceneExit(void) -{ +static void sceneExit(void) { // Free the shader program shaderProgramFree(&program); DVLB_Free(vshader_dvlb); } -int main() -{ +int main() { // Initialize graphics gfxInitDefault(); C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); @@ -101,20 +98,18 @@ int main() sceneInit(); // Main loop - while (aptMainLoop()) - { + while (aptMainLoop()) { hidScanInput(); // Respond to user input u32 kDown = hidKeysDown(); - if (kDown & KEY_START) - break; // break in order to return to hbmenu + if (kDown & KEY_START) break; // break in order to return to hbmenu // Render the scene C3D_FrameBegin(C3D_FRAME_SYNCDRAW); - C3D_RenderTargetClear(target, C3D_CLEAR_ALL, CLEAR_COLOR, 0); - C3D_FrameDrawOn(target); - sceneRender(); + C3D_RenderTargetClear(target, C3D_CLEAR_ALL, CLEAR_COLOR, 0); + C3D_FrameDrawOn(target); + sceneRender(); C3D_FrameEnd(0); } From 68a6d73a1851168211ee1dd2047bb3ce41497c3f Mon Sep 17 00:00:00 2001 From: Jonian Guveli Date: Sat, 3 Aug 2024 10:32:26 +0300 Subject: [PATCH 09/11] Libretro: Add support for cheats --- src/libretro_core.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index 3e0436b8..c91460b8 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -381,5 +382,24 @@ void* retro_get_memory_data(uint id) { return nullptr; } -void retro_cheat_set(uint index, bool enabled, const char* code) {} -void retro_cheat_reset() {} +void retro_cheat_set(uint index, bool enabled, const char* code) { + std::string cheatCode = std::regex_replace(code, std::regex("[^0-9a-fA-F]"), ""); + std::vector bytes; + + for (size_t i = 0; i < cheatCode.size(); i += 2) { + std::string hex = cheatCode.substr(i, 2); + bytes.push_back((u8)std::stoul(hex, nullptr, 16)); + } + + u32 id = emulator->getCheats().addCheat(bytes.data(), bytes.size()); + + if (enabled) { + emulator->getCheats().enableCheat(id); + } else { + emulator->getCheats().disableCheat(id); + } +} + +void retro_cheat_reset() { + emulator->getCheats().reset(); +} From 6e65367e07456b0b71cb1bfe945fd6c9a0e2cc3b Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sat, 3 Aug 2024 12:52:52 +0300 Subject: [PATCH 10/11] size_t -> usize --- src/libretro_core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libretro_core.cpp b/src/libretro_core.cpp index c91460b8..b099067f 100644 --- a/src/libretro_core.cpp +++ b/src/libretro_core.cpp @@ -386,7 +386,7 @@ void retro_cheat_set(uint index, bool enabled, const char* code) { std::string cheatCode = std::regex_replace(code, std::regex("[^0-9a-fA-F]"), ""); std::vector bytes; - for (size_t i = 0; i < cheatCode.size(); i += 2) { + for (usize i = 0; i < cheatCode.size(); i += 2) { std::string hex = cheatCode.substr(i, 2); bytes.push_back((u8)std::stoul(hex, nullptr, 16)); } From 85bae2e94eadb187ddbfcf5fe2005ac7bcd3bd5e Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:46:43 +0300 Subject: [PATCH 11/11] HLE DSP: Handle cycle drifting --- include/audio/dsp_core.hpp | 2 +- include/audio/hle_core.hpp | 4 +++- include/audio/null_core.hpp | 2 +- include/audio/teakra_core.hpp | 2 +- src/core/audio/hle_core.cpp | 21 ++++++++++++++------- src/core/audio/null_core.cpp | 2 +- src/emulator.cpp | 2 +- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/include/audio/dsp_core.hpp b/include/audio/dsp_core.hpp index a4fb1ab1..5addfd19 100644 --- a/include/audio/dsp_core.hpp +++ b/include/audio/dsp_core.hpp @@ -43,7 +43,7 @@ namespace Audio { virtual ~DSPCore() {} virtual void reset() = 0; - virtual void runAudioFrame() = 0; + virtual void runAudioFrame(u64 eventTimestamp) = 0; virtual u8* getDspMemory() = 0; virtual u16 recvData(u32 regId) = 0; diff --git a/include/audio/hle_core.hpp b/include/audio/hle_core.hpp index 35c1c1b8..c0e0896f 100644 --- a/include/audio/hle_core.hpp +++ b/include/audio/hle_core.hpp @@ -42,6 +42,7 @@ namespace Audio { return this->bufferID > other.bufferID; } }; + // Buffer of decoded PCM16 samples. TODO: Are there better alternatives to use over deque? using SampleBuffer = std::deque>; @@ -53,6 +54,7 @@ namespace Audio { std::array gain0, gain1, gain2; u32 samplePosition; // Sample number into the current audio buffer + float rateMultiplier; u16 syncCount; u16 currentBufferID; u16 previousBufferID; @@ -185,7 +187,7 @@ namespace Audio { ~HLE_DSP() override {} void reset() override; - void runAudioFrame() override; + void runAudioFrame(u64 eventTimestamp) override; u8* getDspMemory() override { return dspRam.rawMemory.data(); } diff --git a/include/audio/null_core.hpp b/include/audio/null_core.hpp index 7d6f1c9e..bedec8d3 100644 --- a/include/audio/null_core.hpp +++ b/include/audio/null_core.hpp @@ -27,7 +27,7 @@ namespace Audio { ~NullDSP() override {} void reset() override; - void runAudioFrame() override; + void runAudioFrame(u64 eventTimestamp) override; u8* getDspMemory() override { return dspRam.data(); } diff --git a/include/audio/teakra_core.hpp b/include/audio/teakra_core.hpp index 6a011231..17104985 100644 --- a/include/audio/teakra_core.hpp +++ b/include/audio/teakra_core.hpp @@ -83,7 +83,7 @@ namespace Audio { void reset() override; // Run 1 slice of DSP instructions and schedule the next audio frame - void runAudioFrame() override { + void runAudioFrame(u64 eventTimestamp) override { runSlice(); scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::lleSlice * 2); } diff --git a/src/core/audio/hle_core.cpp b/src/core/audio/hle_core.cpp index ffab9301..d1297ad8 100644 --- a/src/core/audio/hle_core.cpp +++ b/src/core/audio/hle_core.cpp @@ -95,7 +95,7 @@ namespace Audio { scheduler.removeEvent(Scheduler::EventType::RunDSP); } - void HLE_DSP::runAudioFrame() { + void HLE_DSP::runAudioFrame(u64 eventTimestamp) { // Signal audio pipe when an audio frame is done if (dspState == DSPState::On) [[likely]] { dspService.triggerPipeEvent(DSPPipeType::Audio); @@ -103,7 +103,10 @@ namespace Audio { // TODO: Should this be called if dspState != DSPState::On? outputFrame(); - scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame); + + // How many cycles we were late + const u64 cycleDrift = scheduler.currentTimestamp - eventTimestamp; + scheduler.addEvent(Scheduler::EventType::RunDSP, scheduler.currentTimestamp + Audio::cyclesPerFrame - cycleDrift); } u16 HLE_DSP::recvData(u32 regId) { @@ -237,10 +240,9 @@ namespace Audio { auto& status = write.sourceStatuses.status[i]; status.enabled = source.enabled; status.syncCount = source.syncCount; - status.currentBufferIDDirty = source.isBufferIDDirty ? 1 : 0; + status.currentBufferIDDirty = (source.isBufferIDDirty ? 1 : 0); status.currentBufferID = source.currentBufferID; status.previousBufferID = source.previousBufferID; - // TODO: Properly update sample position status.samplePosition = source.samplePosition; source.isBufferIDDirty = false; @@ -292,6 +294,10 @@ namespace Audio { source.sourceType = config.monoOrStereo; } + if (config.rateMultiplierDirty) { + source.rateMultiplier = (config.rateMultiplier > 0.f) ? config.rateMultiplier : 1.f; + } + if (config.embeddedBufferDirty) { // Annoyingly, and only for embedded buffer, whether we use config.playPosition depends on the relevant dirty bit const u32 playPosition = config.playPositionDirty ? config.playPosition : 0; @@ -434,7 +440,7 @@ namespace Audio { decodeBuffer(source); } else { - constexpr uint maxSampleCount = Audio::samplesInFrame; + uint maxSampleCount = uint(float(Audio::samplesInFrame) * source.rateMultiplier); uint outputCount = 0; while (outputCount < maxSampleCount) { @@ -447,9 +453,9 @@ namespace Audio { } const uint sampleCount = std::min(maxSampleCount - outputCount, source.currentSamples.size()); - // samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount); - source.currentSamples.erase(source.currentSamples.begin(), source.currentSamples.begin() + sampleCount); + // samples.insert(samples.end(), source.currentSamples.begin(), source.currentSamples.begin() + sampleCount); + source.currentSamples.erase(source.currentSamples.begin(), std::next(source.currentSamples.begin(), sampleCount)); source.samplePosition += sampleCount; outputCount += sampleCount; } @@ -618,6 +624,7 @@ namespace Audio { previousBufferID = 0; currentBufferID = 0; syncCount = 0; + rateMultiplier = 1.f; buffers = {}; } diff --git a/src/core/audio/null_core.cpp b/src/core/audio/null_core.cpp index ec073ae7..93c746cb 100644 --- a/src/core/audio/null_core.cpp +++ b/src/core/audio/null_core.cpp @@ -74,7 +74,7 @@ namespace Audio { scheduler.removeEvent(Scheduler::EventType::RunDSP); } - void NullDSP::runAudioFrame() { + void NullDSP::runAudioFrame(u64 eventTimestamp) { // Signal audio pipe when an audio frame is done if (dspState == DSPState::On) [[likely]] { dspService.triggerPipeEvent(DSPPipeType::Audio); diff --git a/src/emulator.cpp b/src/emulator.cpp index 921af08f..8ce71e43 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -167,7 +167,7 @@ void Emulator::pollScheduler() { case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break; case Scheduler::EventType::RunDSP: { - dsp->runAudioFrame(); + dsp->runAudioFrame(time); break; }