Merge branch 'master' into shader-decomp

This commit is contained in:
wheremyfoodat 2024-08-08 00:06:48 +03:00
commit 370aa8ee5c
13 changed files with 592 additions and 29 deletions

View file

@ -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];
}
}
}

View file

@ -2,6 +2,7 @@
#include <algorithm>
#include <cassert>
#include <iterator>
#include <thread>
#include <utility>
@ -94,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);
@ -102,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) {
@ -216,6 +220,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];
@ -231,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;
@ -247,6 +255,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;
@ -266,16 +285,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;
@ -285,7 +294,14 @@ 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;
config.embeddedBufferDirty = 0;
if (s32(config.length) >= 0) [[likely]] {
// TODO: Add sample format and channel count
@ -297,7 +313,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,
@ -316,8 +332,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.adpcm_ps),
.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;
@ -369,6 +417,13 @@ namespace Audio {
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);
}
}
void HLE_DSP::generateFrame(DSPSource& source) {
@ -385,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) {
@ -398,9 +453,10 @@ namespace Audio {
}
const uint sampleCount = std::min<s32>(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;
}
}
@ -568,6 +624,7 @@ namespace Audio {
previousBufferID = 0;
currentBufferID = 0;
syncCount = 0;
rateMultiplier = 1.f;
buffers = {};
}

View file

@ -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);

View file

@ -167,7 +167,7 @@ void Emulator::pollScheduler() {
case Scheduler::EventType::UpdateTimers: kernel.pollTimers(); break;
case Scheduler::EventType::RunDSP: {
dsp->runAudioFrame();
dsp->runAudioFrame(time);
break;
}

View file

@ -1,5 +1,6 @@
#include <stdexcept>
#include <cstdio>
#include <regex>
#include <libretro.h>
@ -385,5 +386,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<u8> bytes;
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));
}
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();
}