Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab961e0701 | |||
| 5fc6bf96d8 | |||
| 508f2072a9 | |||
| f4400f3ba2 | |||
| ec634b6a88 | |||
| 324029d4f9 | |||
| 9f6892271f | |||
| 03179ecafe | |||
| 41b8ecdeb6 | |||
| 663ea382da | |||
| d90961122c | |||
| 09126f3a4a | |||
| c715fc4c5e | |||
| 40968e3993 | |||
| cd643ab5c9 | |||
| 180fa6859f | |||
| 188a3cf74c | |||
| 9652973db2 | |||
| 841b295ad0 | |||
| 9e87193725 | |||
| e126021ffe | |||
| 80a56e8893 | |||
| 0e33b19ae0 | |||
| 92c162126b | |||
| fae65d8a72 | |||
| cde658cb27 | |||
| 80ac1331b5 | |||
| 1f1c3bddc0 | |||
| 1b3d86c02f | |||
| 0947f613b1 | |||
| 1b8fe7073b | |||
| bc699ace15 | |||
| f7d59f3e0e | |||
| 2c67bbf609 | |||
| 5692c48ab7 | |||
| 80b4bd3583 | |||
| 22263ccaa4 | |||
| ef8acc9c3d | |||
| 3dcbba38bf | |||
| f4eb7dceaf | |||
| b924c71822 | |||
| 8a497adf85 | |||
| d68856ab12 | |||
| 380658c21d | |||
| 9b38f4fc55 |
@@ -6,8 +6,5 @@ extraction:
|
||||
packages:
|
||||
- "libsdl2-dev"
|
||||
- "qtmultimedia5-dev"
|
||||
- "clang-format-10"
|
||||
- "libtbb-dev"
|
||||
- "libjack-jackd2-dev"
|
||||
- "doxygen"
|
||||
- "graphviz"
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ if(ENABLE_QT)
|
||||
set(QT_PREFIX_HINT)
|
||||
if(YUZU_USE_BUNDLED_QT)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.12.0-msvc2017_64)
|
||||
set(QT_VER qt-5.12.8-msvc2017_64)
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
|
||||
endif()
|
||||
|
||||
@@ -12,16 +12,32 @@ add_library(audio_core STATIC
|
||||
buffer.h
|
||||
codec.cpp
|
||||
codec.h
|
||||
command_generator.cpp
|
||||
command_generator.h
|
||||
common.h
|
||||
effect_context.cpp
|
||||
effect_context.h
|
||||
info_updater.cpp
|
||||
info_updater.h
|
||||
memory_pool.cpp
|
||||
memory_pool.h
|
||||
mix_context.cpp
|
||||
mix_context.h
|
||||
null_sink.h
|
||||
sink.h
|
||||
sink_context.cpp
|
||||
sink_context.h
|
||||
sink_details.cpp
|
||||
sink_details.h
|
||||
sink_stream.h
|
||||
splitter_context.cpp
|
||||
splitter_context.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
time_stretch.cpp
|
||||
time_stretch.h
|
||||
voice_context.cpp
|
||||
voice_context.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
)
|
||||
|
||||
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
|
||||
return output;
|
||||
}
|
||||
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
|
||||
const std::array<s16, 512>& lut = [pitch] {
|
||||
if (pitch > 0xaaaa) {
|
||||
return curve_lut0;
|
||||
}
|
||||
if (pitch <= 0x8000) {
|
||||
return curve_lut1;
|
||||
}
|
||||
return curve_lut2;
|
||||
}();
|
||||
|
||||
std::size_t index{};
|
||||
|
||||
for (std::size_t i = 0; i < sample_count; i++) {
|
||||
const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
|
||||
const auto l0 = lut[lut_index + 0];
|
||||
const auto l1 = lut[lut_index + 1];
|
||||
const auto l2 = lut[lut_index + 2];
|
||||
const auto l3 = lut[lut_index + 3];
|
||||
|
||||
const auto s0 = static_cast<s32>(input[index]);
|
||||
const auto s1 = static_cast<s32>(input[index + 1]);
|
||||
const auto s2 = static_cast<s32>(input[index + 2]);
|
||||
const auto s3 = static_cast<s32>(input[index + 3]);
|
||||
|
||||
output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
|
||||
fraction += pitch;
|
||||
index += (fraction >> 15);
|
||||
fraction &= 0x7fff;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
|
||||
return Interpolate(state, std::move(input), ratio);
|
||||
}
|
||||
|
||||
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
+198
-399
@@ -2,95 +2,49 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/audio_out.h"
|
||||
#include "audio_core/audio_renderer.h"
|
||||
#include "audio_core/codec.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/info_updater.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr u32 STREAM_SAMPLE_RATE{48000};
|
||||
constexpr u32 STREAM_NUM_CHANNELS{2};
|
||||
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
|
||||
class AudioRenderer::VoiceState {
|
||||
public:
|
||||
bool IsPlaying() const {
|
||||
return is_in_use && info.play_state == PlayState::Started;
|
||||
}
|
||||
|
||||
const VoiceOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const VoiceInfo& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
VoiceInfo& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void SetWaveIndex(std::size_t index);
|
||||
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources);
|
||||
void UpdateState();
|
||||
void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
|
||||
|
||||
private:
|
||||
bool is_in_use{};
|
||||
bool is_refresh_pending{};
|
||||
std::size_t wave_index{};
|
||||
std::size_t offset{};
|
||||
Codec::ADPCMState adpcm_state{};
|
||||
InterpolationState interp_state{};
|
||||
std::vector<s16> samples;
|
||||
VoiceOutStatus out_status{};
|
||||
VoiceInfo info{};
|
||||
};
|
||||
|
||||
class AudioRenderer::EffectState {
|
||||
public:
|
||||
const EffectOutStatus& GetOutStatus() const {
|
||||
return out_status;
|
||||
}
|
||||
|
||||
const EffectInStatus& GetInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
EffectInStatus& GetInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
void UpdateState(Core::Memory::Memory& memory);
|
||||
|
||||
private:
|
||||
EffectOutStatus out_status{};
|
||||
EffectInStatus info{};
|
||||
};
|
||||
|
||||
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
|
||||
AudioRendererParameter params,
|
||||
AudioCommon::AudioRendererParameter params,
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event,
|
||||
std::size_t instance_number)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
|
||||
: worker_params{params}, buffer_event{buffer_event},
|
||||
memory_pool_info(params.effect_count + params.voice_count * 4),
|
||||
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
|
||||
sink_context(params.sink_count), splitter_context(),
|
||||
voices(params.voice_count), memory{memory_},
|
||||
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
|
||||
memory),
|
||||
temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
|
||||
behavior_info.SetUserRevision(params.revision);
|
||||
splitter_context.Initialize(behavior_info, params.splitter_count,
|
||||
params.num_splitter_send_channels);
|
||||
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
|
||||
fmt::format("AudioRenderer-Instance{}", instance_number),
|
||||
[=]() { buffer_event->Signal(); });
|
||||
stream =
|
||||
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
|
||||
fmt::format("AudioRenderer-Instance{}", instance_number),
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
QueueMixedBuffer(2);
|
||||
QueueMixedBuffer(3);
|
||||
}
|
||||
|
||||
AudioRenderer::~AudioRenderer() = default;
|
||||
@@ -111,355 +65,200 @@ Stream::State AudioRenderer::GetStreamState() const {
|
||||
return stream->GetState();
|
||||
}
|
||||
|
||||
ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
|
||||
// Copy UpdateDataHeader struct
|
||||
UpdateDataHeader config{};
|
||||
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
|
||||
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
|
||||
|
||||
if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
|
||||
return Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// Copy MemoryPoolInfo structs
|
||||
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
|
||||
std::memcpy(mem_pool_info.data(),
|
||||
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy voice resources
|
||||
const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size};
|
||||
std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
|
||||
sizeof(VoiceResourceInformation) * voice_resources.size());
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size};
|
||||
for (auto& voice : voices) {
|
||||
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
|
||||
voice_offset += sizeof(VoiceInfo);
|
||||
}
|
||||
|
||||
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size +
|
||||
config.voices_size};
|
||||
for (auto& effect : effects) {
|
||||
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
|
||||
effect_offset += sizeof(EffectInStatus);
|
||||
}
|
||||
|
||||
// Update memory pool state
|
||||
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
|
||||
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
|
||||
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Attached;
|
||||
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
|
||||
memory_pool[index].state = MemoryPoolStates::Detached;
|
||||
}
|
||||
}
|
||||
|
||||
// Update voices
|
||||
for (auto& voice : voices) {
|
||||
voice.UpdateState();
|
||||
if (!voice.GetInfo().is_in_use) {
|
||||
continue;
|
||||
}
|
||||
if (voice.GetInfo().is_new) {
|
||||
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& effect : effects) {
|
||||
effect.UpdateState(memory);
|
||||
}
|
||||
|
||||
// Release previous buffers and queue next ones for playback
|
||||
ReleaseAndQueueBuffers();
|
||||
|
||||
// Copy output header
|
||||
UpdateDataHeader response_data{worker_params};
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
response_data.render_info = sizeof(RendererInfo);
|
||||
response_data.total_size += sizeof(RendererInfo);
|
||||
}
|
||||
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||
|
||||
// Copy output memory pool entries
|
||||
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
|
||||
response_data.memory_pools_size);
|
||||
|
||||
// Copy output voice status
|
||||
std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
|
||||
for (const auto& voice : voices) {
|
||||
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
|
||||
sizeof(VoiceOutStatus));
|
||||
voice_out_status_offset += sizeof(VoiceOutStatus);
|
||||
}
|
||||
|
||||
std::size_t effect_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.voice_resource_size};
|
||||
for (const auto& effect : effects) {
|
||||
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
|
||||
sizeof(EffectOutStatus));
|
||||
effect_out_status_offset += sizeof(EffectOutStatus);
|
||||
}
|
||||
|
||||
// Update behavior info output
|
||||
const std::size_t behavior_out_status_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.effects_size + response_data.sinks_size +
|
||||
response_data.performance_manager_size};
|
||||
|
||||
if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info output parameters");
|
||||
return Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
const std::size_t renderer_info_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.effects_size + response_data.sinks_size +
|
||||
response_data.performance_manager_size + response_data.behavior_size};
|
||||
RendererInfo renderer_info{};
|
||||
renderer_info.elasped_frame_count = elapsed_frame_count;
|
||||
std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
|
||||
sizeof(RendererInfo));
|
||||
}
|
||||
|
||||
return MakeResult(output_params);
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
|
||||
wave_index = index & 3;
|
||||
is_refresh_pending = true;
|
||||
}
|
||||
|
||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
|
||||
std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
if (!IsPlaying()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_refresh_pending) {
|
||||
RefreshBuffer(memory, voice_resources);
|
||||
}
|
||||
|
||||
const std::size_t max_size{samples.size() - offset};
|
||||
const std::size_t dequeue_offset{offset};
|
||||
std::size_t size{sample_count * STREAM_NUM_CHANNELS};
|
||||
if (size > max_size) {
|
||||
size = max_size;
|
||||
}
|
||||
|
||||
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
|
||||
offset += size;
|
||||
|
||||
const auto& wave_buffer{info.wave_buffer[wave_index]};
|
||||
if (offset == samples.size()) {
|
||||
offset = 0;
|
||||
|
||||
if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
|
||||
SetWaveIndex(wave_index + 1);
|
||||
}
|
||||
|
||||
if (wave_buffer.buffer_sz) {
|
||||
out_status.wave_buffer_consumed++;
|
||||
}
|
||||
|
||||
if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
|
||||
info.play_state = PlayState::Paused;
|
||||
}
|
||||
}
|
||||
|
||||
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::UpdateState() {
|
||||
if (is_in_use && !info.is_in_use) {
|
||||
// No longer in use, reset state
|
||||
is_refresh_pending = true;
|
||||
wave_index = 0;
|
||||
offset = 0;
|
||||
out_status = {};
|
||||
}
|
||||
is_in_use = info.is_in_use;
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
|
||||
const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
|
||||
std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
|
||||
memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
|
||||
|
||||
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
|
||||
case Codec::PcmFormat::Int16: {
|
||||
// PCM16 is played as-is
|
||||
break;
|
||||
}
|
||||
case Codec::PcmFormat::Adpcm: {
|
||||
// Decode ADPCM to PCM16
|
||||
Codec::ADPCM_Coeff coeffs;
|
||||
memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
|
||||
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
|
||||
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (info.channel_count) {
|
||||
case 1: {
|
||||
// 1 channel is upsampled to 2 channel
|
||||
samples.resize(new_samples.size() * 2);
|
||||
|
||||
for (std::size_t index = 0; index < new_samples.size(); ++index) {
|
||||
auto sample = static_cast<float>(new_samples[index]);
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
samples[index * 2] = static_cast<s16>(sample * info.volume);
|
||||
samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// 2 channel is played as is
|
||||
samples = std::move(new_samples);
|
||||
const std::size_t sample_count = (samples.size() / 2);
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
const std::size_t index_l = index * 2;
|
||||
const std::size_t index_r = index * 2 + 1;
|
||||
|
||||
auto sample_l = static_cast<float>(samples[index_l]);
|
||||
auto sample_r = static_cast<float>(samples[index_r]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample_l *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
if (voice_resources[1]->in_use) {
|
||||
sample_r *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
|
||||
samples[index_l] = static_cast<s16>(sample_l * info.volume);
|
||||
samples[index_r] = static_cast<s16>(sample_r * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
samples.resize((new_samples.size() / 6) * 2);
|
||||
const std::size_t sample_count = samples.size() / 2;
|
||||
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
auto FL = static_cast<float>(new_samples[index * 6]);
|
||||
auto FR = static_cast<float>(new_samples[index * 6 + 1]);
|
||||
auto FC = static_cast<float>(new_samples[index * 6 + 2]);
|
||||
auto BL = static_cast<float>(new_samples[index * 6 + 4]);
|
||||
auto BR = static_cast<float>(new_samples[index * 6 + 5]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
FL *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
if (voice_resources[1]->in_use) {
|
||||
FR *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
if (voice_resources[2]->in_use) {
|
||||
FC *= voice_resources[2]->mix_volumes[2];
|
||||
}
|
||||
if (voice_resources[4]->in_use) {
|
||||
BL *= voice_resources[4]->mix_volumes[4];
|
||||
}
|
||||
if (voice_resources[5]->in_use) {
|
||||
BR *= voice_resources[5]->mix_volumes[5];
|
||||
}
|
||||
|
||||
samples[index * 2] =
|
||||
static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
|
||||
samples[index * 2 + 1] =
|
||||
static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
|
||||
break;
|
||||
}
|
||||
|
||||
// Only interpolate when necessary, expensive.
|
||||
if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
|
||||
samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
|
||||
STREAM_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
is_refresh_pending = false;
|
||||
}
|
||||
|
||||
void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
|
||||
if (info.is_new) {
|
||||
out_status.state = EffectStatus::New;
|
||||
} else {
|
||||
if (info.type == Effect::Aux) {
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
|
||||
"Aux buffers tried to update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr s16 ClampToS16(s32 value) {
|
||||
return static_cast<s16>(std::clamp(value, -32768, 32767));
|
||||
}
|
||||
|
||||
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
|
||||
std::vector<u8>& output_params) {
|
||||
|
||||
InfoUpdater info_updater{input_params, output_params, behavior_info};
|
||||
|
||||
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update memory pool parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
|
||||
LOG_ERROR(Audio, "Failed to update voice parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Deal with stopped audio renderer but updates still taking place
|
||||
if (!info_updater.UpdateEffects(effect_context, true)) {
|
||||
LOG_ERROR(Audio, "Failed to update effect parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsSplitterSupported()) {
|
||||
if (!info_updater.UpdateSplitterInfo(splitter_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update splitter parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
|
||||
splitter_context, effect_context);
|
||||
|
||||
if (mix_result.IsError()) {
|
||||
LOG_ERROR(Audio, "Failed to update mix parameters");
|
||||
return mix_result;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Sinks
|
||||
if (!info_updater.UpdateSinks(sink_context)) {
|
||||
LOG_ERROR(Audio, "Failed to update sink parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Performance buffer
|
||||
if (!info_updater.UpdatePerformanceBuffer()) {
|
||||
LOG_ERROR(Audio, "Failed to update performance buffer parameters");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!info_updater.UpdateErrorInfo(behavior_info)) {
|
||||
LOG_ERROR(Audio, "Failed to update error info");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
|
||||
LOG_ERROR(Audio, "Failed to update renderer info");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
// TODO(ogniK): Statistics
|
||||
|
||||
if (!info_updater.WriteOutputHeader()) {
|
||||
LOG_ERROR(Audio, "Failed to write output header");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Check when all sections are implemented
|
||||
|
||||
if (!info_updater.CheckConsumedSize()) {
|
||||
LOG_ERROR(Audio, "Audio buffers were not consumed!");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
ReleaseAndQueueBuffers();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
constexpr std::size_t BUFFER_SIZE{512};
|
||||
command_generator.PreCommand();
|
||||
// Clear mix buffers before our next operation
|
||||
command_generator.ClearMixBuffers();
|
||||
|
||||
// If the splitter is not in use, sort our mixes
|
||||
if (!splitter_context.UsingSplitter()) {
|
||||
mix_context.SortInfo();
|
||||
}
|
||||
// Sort our voices
|
||||
voice_context.SortInfo();
|
||||
|
||||
// Handle samples
|
||||
command_generator.GenerateVoiceCommands();
|
||||
command_generator.GenerateSubMixCommands();
|
||||
command_generator.GenerateFinalMixCommands();
|
||||
|
||||
command_generator.PostCommand();
|
||||
// Base sample size
|
||||
std::size_t BUFFER_SIZE{worker_params.sample_count};
|
||||
// Samples
|
||||
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
|
||||
// Make sure to clear our samples
|
||||
std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
|
||||
|
||||
for (auto& voice : voices) {
|
||||
if (!voice.IsPlaying()) {
|
||||
continue;
|
||||
}
|
||||
VoiceChannelHolder resources{};
|
||||
for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
|
||||
const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
|
||||
resources[channel] = &voice_resources[channel_resource_id];
|
||||
if (sink_context.InUse()) {
|
||||
const auto stream_channel_count = stream->GetNumChannels();
|
||||
const auto buffer_offsets = sink_context.OutputBuffers();
|
||||
const auto channel_count = buffer_offsets.size();
|
||||
const auto& final_mix = mix_context.GetFinalMixInfo();
|
||||
const auto& in_params = final_mix.GetInParams();
|
||||
std::vector<s32*> mix_buffers(channel_count);
|
||||
for (std::size_t i = 0; i < channel_count; i++) {
|
||||
mix_buffers[i] =
|
||||
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
|
||||
}
|
||||
|
||||
std::size_t offset{};
|
||||
s64 samples_remaining{BUFFER_SIZE};
|
||||
while (samples_remaining > 0) {
|
||||
const std::vector<s16> samples{
|
||||
voice.DequeueSamples(samples_remaining, memory, resources)};
|
||||
for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
|
||||
if (channel_count == 1) {
|
||||
const auto sample = ClampToS16(mix_buffers[0][i]);
|
||||
buffer[i * stream_channel_count + 0] = sample;
|
||||
if (stream_channel_count > 1) {
|
||||
buffer[i * stream_channel_count + 1] = sample;
|
||||
}
|
||||
if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 2] = sample;
|
||||
buffer[i * stream_channel_count + 4] = sample;
|
||||
buffer[i * stream_channel_count + 5] = sample;
|
||||
}
|
||||
} else if (channel_count == 2) {
|
||||
const auto l_sample = ClampToS16(mix_buffers[0][i]);
|
||||
const auto r_sample = ClampToS16(mix_buffers[1][i]);
|
||||
if (stream_channel_count == 1) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
} else if (stream_channel_count == 2) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
buffer[i * stream_channel_count + 1] = r_sample;
|
||||
} else if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 0] = l_sample;
|
||||
buffer[i * stream_channel_count + 1] = r_sample;
|
||||
|
||||
if (samples.empty()) {
|
||||
break;
|
||||
}
|
||||
buffer[i * stream_channel_count + 2] =
|
||||
ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
|
||||
|
||||
samples_remaining -= samples.size() / stream->GetNumChannels();
|
||||
buffer[i * stream_channel_count + 4] = l_sample;
|
||||
buffer[i * stream_channel_count + 5] = r_sample;
|
||||
}
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
const s32 buffer_sample{buffer[offset]};
|
||||
buffer[offset++] =
|
||||
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
|
||||
} else if (channel_count == 6) {
|
||||
const auto fl_sample = ClampToS16(mix_buffers[0][i]);
|
||||
const auto fr_sample = ClampToS16(mix_buffers[1][i]);
|
||||
const auto fc_sample = ClampToS16(mix_buffers[2][i]);
|
||||
const auto lf_sample = ClampToS16(mix_buffers[3][i]);
|
||||
const auto bl_sample = ClampToS16(mix_buffers[4][i]);
|
||||
const auto br_sample = ClampToS16(mix_buffers[5][i]);
|
||||
|
||||
if (stream_channel_count == 1) {
|
||||
buffer[i * stream_channel_count + 0] = fc_sample;
|
||||
} else if (stream_channel_count == 2) {
|
||||
buffer[i * stream_channel_count + 0] =
|
||||
static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
|
||||
0.2612f * static_cast<float>(fc_sample) +
|
||||
0.3694f * static_cast<float>(bl_sample));
|
||||
buffer[i * stream_channel_count + 1] =
|
||||
static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
|
||||
0.2612f * static_cast<float>(fc_sample) +
|
||||
0.3694f * static_cast<float>(br_sample));
|
||||
} else if (stream_channel_count == 6) {
|
||||
buffer[i * stream_channel_count + 0] = fl_sample;
|
||||
buffer[i * stream_channel_count + 1] = fr_sample;
|
||||
buffer[i * stream_channel_count + 2] = fc_sample;
|
||||
buffer[i * stream_channel_count + 3] = lf_sample;
|
||||
buffer[i * stream_channel_count + 4] = bl_sample;
|
||||
buffer[i * stream_channel_count + 5] = br_sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
elapsed_frame_count++;
|
||||
voice_context.UpdateStateByDspShared();
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
|
||||
+20
-204
@@ -9,8 +9,15 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/command_generator.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/memory_pool.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/sink_context.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
@@ -30,220 +37,25 @@ class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
using DSPStateHolder = std::array<VoiceState*, 6>;
|
||||
|
||||
class AudioOut;
|
||||
|
||||
enum class PlayState : u8 {
|
||||
Started = 0,
|
||||
Stopped = 1,
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
enum class Effect : u8 {
|
||||
None = 0,
|
||||
Aux = 2,
|
||||
};
|
||||
|
||||
enum class EffectStatus : u8 {
|
||||
None = 0,
|
||||
New = 1,
|
||||
};
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le submix_count;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
enum class MemoryPoolStates : u32 { // Should be LE
|
||||
Invalid = 0x0,
|
||||
Unknown = 0x1,
|
||||
RequestDetach = 0x2,
|
||||
Detached = 0x3,
|
||||
RequestAttach = 0x4,
|
||||
Attached = 0x5,
|
||||
Released = 0x6,
|
||||
};
|
||||
|
||||
struct MemoryPoolEntry {
|
||||
MemoryPoolStates state;
|
||||
u32_le unknown_4;
|
||||
u32_le unknown_8;
|
||||
u32_le unknown_c;
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
|
||||
|
||||
struct MemoryPoolInfo {
|
||||
u64_le pool_address;
|
||||
u64_le pool_size;
|
||||
MemoryPoolStates pool_state;
|
||||
INSERT_PADDING_WORDS(3); // Unknown
|
||||
};
|
||||
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
|
||||
struct BiquadFilter {
|
||||
u8 enable;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
std::array<s16_le, 3> numerator;
|
||||
std::array<s16_le, 2> denominator;
|
||||
};
|
||||
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
|
||||
|
||||
struct WaveBuffer {
|
||||
u64_le buffer_addr;
|
||||
u64_le buffer_sz;
|
||||
s32_le start_sample_offset;
|
||||
s32_le end_sample_offset;
|
||||
u8 is_looping;
|
||||
u8 end_of_stream;
|
||||
u8 sent_to_server;
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64 context_addr;
|
||||
u64 context_sz;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
||||
|
||||
struct VoiceResourceInformation {
|
||||
s32_le id{};
|
||||
std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(11);
|
||||
};
|
||||
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
|
||||
|
||||
struct VoiceInfo {
|
||||
u32_le id;
|
||||
u32_le node_id;
|
||||
u8 is_new;
|
||||
u8 is_in_use;
|
||||
PlayState play_state;
|
||||
u8 sample_format;
|
||||
u32_le sample_rate;
|
||||
u32_le priority;
|
||||
u32_le sorting_order;
|
||||
u32_le channel_count;
|
||||
float_le pitch;
|
||||
float_le volume;
|
||||
std::array<BiquadFilter, 2> biquad_filter;
|
||||
u32_le wave_buffer_count;
|
||||
u32_le wave_buffer_head;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u64_le additional_params_addr;
|
||||
u64_le additional_params_sz;
|
||||
u32_le mix_id;
|
||||
u32_le splitter_info_id;
|
||||
std::array<WaveBuffer, 4> wave_buffer;
|
||||
std::array<u32_le, 6> voice_channel_resource_ids;
|
||||
INSERT_PADDING_BYTES(24);
|
||||
};
|
||||
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
|
||||
|
||||
struct VoiceOutStatus {
|
||||
u64_le played_sample_count;
|
||||
u32_le wave_buffer_consumed;
|
||||
u32_le voice_drops_count;
|
||||
};
|
||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
|
||||
|
||||
struct AuxInfo {
|
||||
std::array<u8, 24> input_mix_buffers;
|
||||
std::array<u8, 24> output_mix_buffers;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le sample_rate; // Stored in the aux buffer currently
|
||||
u32_le sample_count;
|
||||
u64_le send_buffer_info;
|
||||
u64_le send_buffer_base;
|
||||
|
||||
u64_le return_buffer_info;
|
||||
u64_le return_buffer_base;
|
||||
};
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
|
||||
|
||||
struct EffectInStatus {
|
||||
Effect type;
|
||||
u8 is_new;
|
||||
u8 is_enabled;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u32_le mix_id;
|
||||
u64_le buffer_base;
|
||||
u64_le buffer_sz;
|
||||
s32_le priority;
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
std::array<u8, 0xa0> raw;
|
||||
AuxInfo aux_info;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
|
||||
|
||||
struct EffectOutStatus {
|
||||
EffectStatus state;
|
||||
INSERT_PADDING_BYTES(0xf);
|
||||
};
|
||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
|
||||
|
||||
struct RendererInfo {
|
||||
u64_le elasped_frame_count{};
|
||||
INSERT_PADDING_WORDS(2);
|
||||
};
|
||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
explicit UpdateDataHeader(const AudioRendererParameter& config) {
|
||||
revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
|
||||
behavior_size = 0xb0;
|
||||
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
|
||||
voices_size = config.voice_count * 0x10;
|
||||
voice_resource_size = 0x0;
|
||||
effects_size = config.effect_count * 0x10;
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
render_info = 0;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||
effects_size + sinks_size + performance_manager_size;
|
||||
}
|
||||
|
||||
u32_le revision{};
|
||||
u32_le behavior_size{};
|
||||
u32_le memory_pools_size{};
|
||||
u32_le voices_size{};
|
||||
u32_le voice_resource_size{};
|
||||
u32_le effects_size{};
|
||||
u32_le mixes_size{};
|
||||
u32_le sinks_size{};
|
||||
u32_le performance_manager_size{};
|
||||
u32_le splitter_size{};
|
||||
u32_le render_info{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
u32_le total_size{};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
|
||||
|
||||
class AudioRenderer {
|
||||
public:
|
||||
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
|
||||
AudioRendererParameter params,
|
||||
AudioCommon::AudioRendererParameter params,
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
|
||||
~AudioRenderer();
|
||||
|
||||
ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
|
||||
ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
|
||||
std::vector<u8>& output_params);
|
||||
void QueueMixedBuffer(Buffer::Tag tag);
|
||||
void ReleaseAndQueueBuffers();
|
||||
u32 GetSampleRate() const;
|
||||
@@ -252,19 +64,23 @@ public:
|
||||
Stream::State GetStreamState() const;
|
||||
|
||||
private:
|
||||
class EffectState;
|
||||
class VoiceState;
|
||||
BehaviorInfo behavior_info{};
|
||||
|
||||
AudioRendererParameter worker_params;
|
||||
AudioCommon::AudioRendererParameter worker_params;
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event;
|
||||
std::vector<ServerMemoryPoolInfo> memory_pool_info;
|
||||
VoiceContext voice_context;
|
||||
EffectContext effect_context;
|
||||
MixContext mix_context;
|
||||
SinkContext sink_context;
|
||||
SplitterContext splitter_context;
|
||||
std::vector<VoiceState> voices;
|
||||
std::vector<VoiceResourceInformation> voice_resources;
|
||||
std::vector<EffectState> effects;
|
||||
std::unique_ptr<AudioOut> audio_out;
|
||||
StreamPtr stream;
|
||||
Core::Memory::Memory& memory;
|
||||
CommandGenerator command_generator;
|
||||
std::size_t elapsed_frame_count{};
|
||||
std::vector<s32> temp_mix_buffer{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -9,39 +9,11 @@
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
|
||||
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
|
||||
BehaviorInfo::~BehaviorInfo() = default;
|
||||
|
||||
bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
|
||||
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
InParams params{};
|
||||
std::memcpy(¶ms, buffer.data() + offset, sizeof(InParams));
|
||||
|
||||
if (!IsValidRevision(params.revision)) {
|
||||
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user_revision != params.revision) {
|
||||
LOG_ERROR(Audio,
|
||||
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
|
||||
user_revision, params.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClearError();
|
||||
UpdateFlags(params.flags);
|
||||
|
||||
// TODO(ogniK): Check input params size when InfoUpdater is used
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
|
||||
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
|
||||
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
|
||||
user_revision = revision;
|
||||
}
|
||||
|
||||
u32_le BehaviorInfo::GetUserRevision() const {
|
||||
return user_revision;
|
||||
}
|
||||
|
||||
u32_le BehaviorInfo::GetProcessRevision() const {
|
||||
return process_revision;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
|
||||
return IsRevisionSupported(2, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterSupported() const {
|
||||
return IsRevisionSupported(2, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
|
||||
return IsRevisionSupported(3, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(3, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
|
||||
return IsRevisionSupported(5, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
|
||||
return IsRevisionSupported(4, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(4, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
|
||||
return IsRevisionSupported(1, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(1, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
|
||||
return IsRevisionSupported(5, user_revision);
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
|
||||
return (flags & 1) != 0;
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
|
||||
return AudioCommon::IsRevisionSupported(7, user_revision);
|
||||
}
|
||||
|
||||
bool BehaviorInfo::IsSplitterBugFixed() const {
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision);
|
||||
}
|
||||
|
||||
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
|
||||
dst.error_count = static_cast<u32>(error_count);
|
||||
std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -14,30 +14,6 @@
|
||||
namespace AudioCore {
|
||||
class BehaviorInfo {
|
||||
public:
|
||||
explicit BehaviorInfo();
|
||||
~BehaviorInfo();
|
||||
|
||||
bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
|
||||
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
|
||||
|
||||
void ClearError();
|
||||
void UpdateFlags(u64_le dest_flags);
|
||||
void SetUserRevision(u32_le revision);
|
||||
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
bool IsSplitterSupported() const;
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
bool IsMemoryPoolForceMappingEnabled() const;
|
||||
|
||||
private:
|
||||
u32_le process_revision{};
|
||||
u32_le user_revision{};
|
||||
u64_le flags{};
|
||||
|
||||
struct ErrorInfo {
|
||||
u32_le result{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -45,9 +21,6 @@ private:
|
||||
};
|
||||
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
|
||||
|
||||
std::array<ErrorInfo, 10> errors{};
|
||||
std::size_t error_count{};
|
||||
|
||||
struct InParams {
|
||||
u32_le revision{};
|
||||
u32_le padding{};
|
||||
@@ -61,6 +34,39 @@ private:
|
||||
INSERT_PADDING_BYTES(12);
|
||||
};
|
||||
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
|
||||
|
||||
explicit BehaviorInfo();
|
||||
~BehaviorInfo();
|
||||
|
||||
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
|
||||
|
||||
void ClearError();
|
||||
void UpdateFlags(u64_le dest_flags);
|
||||
void SetUserRevision(u32_le revision);
|
||||
u32_le GetUserRevision() const;
|
||||
u32_le GetProcessRevision() const;
|
||||
|
||||
bool IsAdpcmLoopContextBugFixed() const;
|
||||
bool IsSplitterSupported() const;
|
||||
bool IsLongSizePreDelaySupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
|
||||
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
|
||||
bool IsElapsedFrameCountSupported() const;
|
||||
bool IsMemoryPoolForceMappingEnabled() const;
|
||||
bool IsFlushVoiceWaveBuffersSupported() const;
|
||||
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
|
||||
bool IsVoicePitchAndSrcSkippedSupported() const;
|
||||
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
|
||||
bool IsSplitterBugFixed() const;
|
||||
void CopyErrorInfo(OutParams& dst);
|
||||
|
||||
private:
|
||||
u32_le process_revision{};
|
||||
u32_le user_revision{};
|
||||
u64_le flags{};
|
||||
std::array<ErrorInfo, 10> errors{};
|
||||
std::size_t error_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -0,0 +1,976 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/command_generator.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace {
|
||||
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
|
||||
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
|
||||
|
||||
template <std::size_t N>
|
||||
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
|
||||
for (std::size_t j = 0; j < N; j++) {
|
||||
output[i + j] +=
|
||||
static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
|
||||
s32 x = 0;
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
x = static_cast<s32>(static_cast<float>(input[i]) * gain);
|
||||
output[i] += x;
|
||||
gain += delta;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
|
||||
gain += delta;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
|
||||
}
|
||||
}
|
||||
|
||||
s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
|
||||
const bool positive = first_sample > 0;
|
||||
auto final_sample = std::abs(first_sample);
|
||||
for (s32 i = 0; i < sample_count; i++) {
|
||||
final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
|
||||
if (positive) {
|
||||
output[i] += final_sample;
|
||||
} else {
|
||||
output[i] -= final_sample;
|
||||
}
|
||||
}
|
||||
if (positive) {
|
||||
return final_sample;
|
||||
} else {
|
||||
return -final_sample;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
SplitterContext& splitter_context, EffectContext& effect_context,
|
||||
Core::Memory::Memory& memory)
|
||||
: worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
|
||||
splitter_context(splitter_context), effect_context(effect_context), memory(memory),
|
||||
mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
|
||||
worker_params.sample_count),
|
||||
sample_buffer(MIX_BUFFER_SIZE),
|
||||
depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
|
||||
worker_params.sample_count) {}
|
||||
CommandGenerator::~CommandGenerator() = default;
|
||||
|
||||
void CommandGenerator::ClearMixBuffers() {
|
||||
std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
|
||||
std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
|
||||
// std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceCommands() {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
|
||||
}
|
||||
// Grab all our voices
|
||||
const auto voice_count = voice_context.GetVoiceCount();
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
auto& voice_info = voice_context.GetSortedInfo(i);
|
||||
// Update voices and check if we should queue them
|
||||
if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Queue our voice
|
||||
GenerateVoiceCommand(voice_info);
|
||||
}
|
||||
// Update our splitters
|
||||
splitter_context.UpdateInternalState();
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto channel_count = in_params.channel_count;
|
||||
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
const auto resource_id = in_params.voice_channel_resource_id[channel];
|
||||
auto& dsp_state = voice_context.GetDspSharedState(resource_id);
|
||||
auto& channel_resource = voice_context.GetChannelResource(resource_id);
|
||||
|
||||
// Decode our samples for our channel
|
||||
GenerateDataSourceCommand(voice_info, dsp_state, channel);
|
||||
|
||||
if (in_params.should_depop) {
|
||||
in_params.last_volume = 0.0f;
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
|
||||
in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
// Apply a biquad filter if needed
|
||||
GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
|
||||
worker_params.mix_buffer_count, channel);
|
||||
// Base voice volume ramping
|
||||
GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
|
||||
in_params.node_id);
|
||||
in_params.last_volume = in_params.volume;
|
||||
|
||||
if (in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
// If we're using a mix id
|
||||
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
|
||||
const auto& dest_mix_params = mix_info.GetInParams();
|
||||
|
||||
// Voice Mixing
|
||||
GenerateVoiceMixCommand(
|
||||
channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
|
||||
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
|
||||
worker_params.mix_buffer_count + channel, in_params.node_id);
|
||||
|
||||
// Update last mix volumes
|
||||
channel_resource.UpdateLastMixVolumes();
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 base = channel;
|
||||
while (auto* destination_data =
|
||||
GetDestinationData(in_params.splitter_info_id, base)) {
|
||||
base += channel_count;
|
||||
|
||||
if (!destination_data->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
if (destination_data->GetMixId() >= mix_context.GetCount()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
|
||||
const auto& dest_mix_params = mix_info.GetInParams();
|
||||
GenerateVoiceMixCommand(
|
||||
destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
|
||||
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
|
||||
worker_params.mix_buffer_count + channel, in_params.node_id);
|
||||
destination_data->MarkDirty();
|
||||
}
|
||||
}
|
||||
// Update biquad filter enabled states
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
|
||||
in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateSubMixCommands() {
|
||||
const auto mix_count = mix_context.GetCount();
|
||||
for (std::size_t i = 0; i < mix_count; i++) {
|
||||
auto& mix_info = mix_context.GetSortedInfo(i);
|
||||
const auto& in_params = mix_info.GetInParams();
|
||||
if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
|
||||
continue;
|
||||
}
|
||||
GenerateSubMixCommand(mix_info);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateFinalMixCommands() {
|
||||
GenerateFinalMixCommand();
|
||||
}
|
||||
|
||||
void CommandGenerator::PreCommand() {
|
||||
if (!dumping_frame) {
|
||||
return;
|
||||
}
|
||||
for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
|
||||
const auto& base = splitter_context.GetInfo(i);
|
||||
std::string graph = fmt::format("b[{}]", i);
|
||||
auto* head = base.GetHead();
|
||||
while (head != nullptr) {
|
||||
graph += fmt::format("->{}", head->GetMixId());
|
||||
head = head->GetNextDestination();
|
||||
}
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::PostCommand() {
|
||||
if (!dumping_frame) {
|
||||
return;
|
||||
}
|
||||
dumping_frame = false;
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 channel) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto depop = in_params.should_depop;
|
||||
|
||||
if (depop) {
|
||||
if (in_params.mix_id != AudioCommon::NO_MIX) {
|
||||
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
|
||||
const auto& mix_in = mix_info.GetInParams();
|
||||
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
|
||||
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 index{};
|
||||
while (const auto* destination =
|
||||
GetDestinationData(in_params.splitter_info_id, index++)) {
|
||||
if (!destination->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
auto& mix_info = mix_context.GetInfo(destination->GetMixId());
|
||||
const auto& mix_in = mix_info.GetInParams();
|
||||
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (in_params.sample_format) {
|
||||
case SampleFormat::Pcm16:
|
||||
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
|
||||
worker_params.sample_rate, worker_params.sample_count,
|
||||
in_params.node_id);
|
||||
break;
|
||||
case SampleFormat::Adpcm:
|
||||
ASSERT(channel == 0 && in_params.channel_count == 1);
|
||||
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
|
||||
worker_params.sample_rate, worker_params.sample_count,
|
||||
in_params.node_id);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
|
||||
VoiceState& dsp_state,
|
||||
s32 mix_buffer_count, s32 channel) {
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
|
||||
const auto& in_params = voice_info.GetInParams();
|
||||
auto& biquad_filter = in_params.biquad_filter[i];
|
||||
// Check if biquad filter is actually used
|
||||
if (!biquad_filter.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reinitialize our biquad filter state if it was enabled previously
|
||||
if (!in_params.was_biquad_filter_enabled[i]) {
|
||||
dsp_state.biquad_filter_state.fill(0);
|
||||
}
|
||||
|
||||
// Generate biquad filter
|
||||
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
|
||||
// dsp_state.biquad_filter_state,
|
||||
// mix_buffer_count + channel, mix_buffer_count +
|
||||
// channel, worker_params.sample_count,
|
||||
// voice_info.GetInParams().node_id);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
|
||||
s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
|
||||
std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
|
||||
"input_mix_buffer={}, output_mix_buffer={}",
|
||||
node_id, input_offset, output_offset);
|
||||
}
|
||||
const auto* input = GetMixBuffer(input_offset);
|
||||
auto* output = GetMixBuffer(output_offset);
|
||||
|
||||
// Biquad filter parameters
|
||||
const auto [n0, n1, n2] = params.numerator;
|
||||
const auto [d0, d1] = params.denominator;
|
||||
|
||||
// Biquad filter states
|
||||
auto [s0, s1] = state;
|
||||
|
||||
constexpr s64 int32_min = std::numeric_limits<s32>::min();
|
||||
constexpr s64 int32_max = std::numeric_limits<s32>::max();
|
||||
|
||||
for (int i = 0; i < sample_count; ++i) {
|
||||
const auto sample = static_cast<s64>(input[i]);
|
||||
const auto f = (sample * n0 + s0 + 0x4000) >> 15;
|
||||
const auto y = std::clamp(f, int32_min, int32_max);
|
||||
s0 = sample * n1 + y * d0 + s1;
|
||||
s1 = sample * n2 + y * d1;
|
||||
output[i] = static_cast<s32>(y);
|
||||
}
|
||||
|
||||
state = {s0, s1};
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
|
||||
std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset) {
|
||||
for (std::size_t i = 0; i < mix_buffer_count; i++) {
|
||||
auto& sample = dsp_state.previous_samples[i];
|
||||
if (sample != 0) {
|
||||
depop_buffer[mix_buffer_offset + i] += sample;
|
||||
sample = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset,
|
||||
s32 sample_rate) {
|
||||
const std::size_t end_offset =
|
||||
std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
|
||||
const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
|
||||
for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
|
||||
if (depop_buffer[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
depop_buffer[i] =
|
||||
ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
|
||||
const std::size_t effect_count = effect_context.GetCount();
|
||||
const auto buffer_offset = mix_info.GetInParams().buffer_offset;
|
||||
for (std::size_t i = 0; i < effect_count; i++) {
|
||||
const auto index = mix_info.GetEffectOrder(i);
|
||||
if (index == AudioCommon::NO_EFFECT_ORDER) {
|
||||
break;
|
||||
}
|
||||
auto* info = effect_context.GetInfo(index);
|
||||
const auto type = info->GetType();
|
||||
|
||||
// TODO(ogniK): Finish remaining effects
|
||||
switch (type) {
|
||||
case EffectType::Aux:
|
||||
GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
case EffectType::I3dl2Reverb:
|
||||
GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
case EffectType::BiquadFilter:
|
||||
GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
info->UpdateForCommandGeneration();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
||||
bool enabled) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
|
||||
const auto channel_count = params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
// TODO(ogniK): Actually implement reverb
|
||||
if (params.input[i] != params.output[i]) {
|
||||
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
|
||||
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
||||
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
|
||||
bool enabled) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
|
||||
const auto channel_count = params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
// TODO(ogniK): Actually implement biquad filter
|
||||
if (params.input[i] != params.output[i]) {
|
||||
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
|
||||
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
|
||||
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
|
||||
auto aux = dynamic_cast<EffectAuxInfo*>(info);
|
||||
const auto& params = aux->GetParams();
|
||||
if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
|
||||
const auto max_channels = params.count;
|
||||
u32 offset{};
|
||||
for (u32 channel = 0; channel < max_channels; channel++) {
|
||||
u32 write_count = 0;
|
||||
if (channel == (max_channels - 1)) {
|
||||
write_count = offset + worker_params.sample_count;
|
||||
}
|
||||
|
||||
const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
|
||||
const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
|
||||
|
||||
if (enabled) {
|
||||
AuxInfoDSP send_info{};
|
||||
AuxInfoDSP recv_info{};
|
||||
memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
|
||||
memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
|
||||
|
||||
WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
|
||||
GetMixBuffer(input_index), worker_params.sample_count, offset,
|
||||
write_count);
|
||||
memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
|
||||
|
||||
const auto samples_read = ReadAuxBuffer(
|
||||
recv_info, aux->GetRecvBuffer(), params.sample_count,
|
||||
GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
|
||||
memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
|
||||
|
||||
if (samples_read != worker_params.sample_count &&
|
||||
samples_read <= params.sample_count) {
|
||||
std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
|
||||
}
|
||||
} else {
|
||||
AuxInfoDSP empty{};
|
||||
memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
|
||||
memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
|
||||
if (output_index != input_index) {
|
||||
std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
|
||||
worker_params.sample_count * sizeof(s32));
|
||||
}
|
||||
}
|
||||
|
||||
offset += worker_params.sample_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
|
||||
if (splitter_id == AudioCommon::NO_SPLITTER) {
|
||||
return nullptr;
|
||||
}
|
||||
return splitter_context.GetDestinationData(splitter_id, index);
|
||||
}
|
||||
|
||||
s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
|
||||
const s32* data, u32 sample_count, u32 write_offset,
|
||||
u32 write_count) {
|
||||
if (max_samples == 0) {
|
||||
return 0;
|
||||
}
|
||||
u32 offset = dsp_info.write_offset + write_offset;
|
||||
if (send_buffer == 0 || offset > max_samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t data_offset{};
|
||||
u32 remaining = sample_count;
|
||||
while (remaining > 0) {
|
||||
// Get position in buffer
|
||||
const auto base = send_buffer + (offset * sizeof(u32));
|
||||
const auto samples_to_grab = std::min(max_samples - offset, remaining);
|
||||
// Write to output
|
||||
memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
|
||||
offset = (offset + samples_to_grab) % max_samples;
|
||||
remaining -= samples_to_grab;
|
||||
data_offset += samples_to_grab;
|
||||
}
|
||||
|
||||
if (write_count != 0) {
|
||||
dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
|
||||
}
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
|
||||
s32* out_data, u32 sample_count, u32 read_offset,
|
||||
u32 read_count) {
|
||||
if (max_samples == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 offset = recv_info.read_offset + read_offset;
|
||||
if (recv_buffer == 0 || offset > max_samples) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 remaining = sample_count;
|
||||
while (remaining > 0) {
|
||||
const auto base = recv_buffer + (offset * sizeof(u32));
|
||||
const auto samples_to_grab = std::min(max_samples - offset, remaining);
|
||||
std::vector<s32> buffer(samples_to_grab);
|
||||
memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
|
||||
std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
|
||||
out_data += samples_to_grab;
|
||||
offset = (offset + samples_to_grab) % max_samples;
|
||||
remaining -= samples_to_grab;
|
||||
}
|
||||
|
||||
if (read_count != 0) {
|
||||
recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
|
||||
}
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
|
||||
s32 channel, s32 node_id) {
|
||||
const auto last = static_cast<s32>(last_volume * 32768.0f);
|
||||
const auto current = static_cast<s32>(current_volume * 32768.0f);
|
||||
const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
|
||||
static_cast<float>(worker_params.sample_count));
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
|
||||
"last_volume={}, current_volume={}",
|
||||
node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
|
||||
last_volume, current_volume);
|
||||
}
|
||||
// Apply generic gain on samples
|
||||
ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
|
||||
worker_params.sample_count);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
|
||||
const MixVolumeBuffer& last_mix_volumes,
|
||||
VoiceState& dsp_state, s32 mix_buffer_offset,
|
||||
s32 mix_buffer_count, s32 voice_index, s32 node_id) {
|
||||
// Loop all our mix buffers
|
||||
for (s32 i = 0; i < mix_buffer_count; i++) {
|
||||
if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
|
||||
const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
|
||||
static_cast<float>(worker_params.sample_count);
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
|
||||
"output={}, last_volume={}, current_volume={}",
|
||||
node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
|
||||
mix_volumes[i]);
|
||||
}
|
||||
|
||||
dsp_state.previous_samples[i] =
|
||||
ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
|
||||
last_mix_volumes[i], delta, worker_params.sample_count);
|
||||
} else {
|
||||
dsp_state.previous_samples[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
|
||||
}
|
||||
auto& in_params = mix_info.GetInParams();
|
||||
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
|
||||
in_params.sample_rate);
|
||||
|
||||
GenerateEffectCommand(mix_info);
|
||||
|
||||
GenerateMixCommands(mix_info);
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
|
||||
if (!mix_info.HasAnyConnection()) {
|
||||
return;
|
||||
}
|
||||
const auto& in_params = mix_info.GetInParams();
|
||||
if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
|
||||
const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
|
||||
const auto& dest_in_params = dest_mix.GetInParams();
|
||||
|
||||
const auto buffer_count = in_params.buffer_count;
|
||||
|
||||
for (s32 i = 0; i < buffer_count; i++) {
|
||||
for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
|
||||
const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
|
||||
if (mixed_volume != 0.0f) {
|
||||
GenerateMixCommand(dest_in_params.buffer_offset + j,
|
||||
in_params.buffer_offset + i, mixed_volume,
|
||||
in_params.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
|
||||
s32 base{};
|
||||
while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
|
||||
if (!destination_data->IsConfigured()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
|
||||
const auto& dest_in_params = dest_mix.GetInParams();
|
||||
const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
|
||||
for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) {
|
||||
const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
|
||||
if (mixed_volume != 0.0f) {
|
||||
GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
|
||||
in_params.node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
|
||||
float volume, s32 node_id) {
|
||||
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
|
||||
node_id, input_offset, output_offset, volume);
|
||||
}
|
||||
|
||||
auto* output = GetMixBuffer(output_offset);
|
||||
const auto* input = GetMixBuffer(input_offset);
|
||||
|
||||
const s32 gain = static_cast<s32>(volume * 32768.0f);
|
||||
// Mix with loop unrolling
|
||||
if (worker_params.sample_count % 4 == 0) {
|
||||
ApplyMix<4>(output, input, gain, worker_params.sample_count);
|
||||
} else if (worker_params.sample_count % 2 == 0) {
|
||||
ApplyMix<2>(output, input, gain, worker_params.sample_count);
|
||||
} else {
|
||||
ApplyMix<1>(output, input, gain, worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandGenerator::GenerateFinalMixCommand() {
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
|
||||
}
|
||||
auto& mix_info = mix_context.GetFinalMixInfo();
|
||||
const auto in_params = mix_info.GetInParams();
|
||||
|
||||
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
|
||||
in_params.sample_rate);
|
||||
|
||||
GenerateEffectCommand(mix_info);
|
||||
|
||||
for (s32 i = 0; i < in_params.buffer_count; i++) {
|
||||
const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(
|
||||
Audio,
|
||||
"(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
|
||||
in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
|
||||
in_params.volume);
|
||||
}
|
||||
ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
|
||||
GetMixBuffer(in_params.buffer_offset + i), gain,
|
||||
worker_params.sample_count);
|
||||
}
|
||||
}
|
||||
|
||||
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 sample_count, s32 channel, std::size_t mix_offset) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
if (wave_buffer.buffer_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
|
||||
return 0;
|
||||
}
|
||||
const auto samples_remaining =
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
|
||||
const auto start_offset =
|
||||
((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
|
||||
sizeof(s16);
|
||||
const auto buffer_pos = wave_buffer.buffer_address + start_offset;
|
||||
const auto samples_processed = std::min(sample_count, samples_remaining);
|
||||
|
||||
if (in_params.channel_count == 1) {
|
||||
std::vector<s16> buffer(samples_processed);
|
||||
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
|
||||
for (std::size_t i = 0; i < buffer.size(); i++) {
|
||||
sample_buffer[mix_offset + i] = buffer[i];
|
||||
}
|
||||
} else {
|
||||
const auto channel_count = in_params.channel_count;
|
||||
std::vector<s16> buffer(samples_processed * channel_count);
|
||||
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
|
||||
|
||||
for (std::size_t i = 0; i < samples_processed; i++) {
|
||||
sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
|
||||
}
|
||||
}
|
||||
|
||||
return samples_processed;
|
||||
}
|
||||
|
||||
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 sample_count, s32 channel, std::size_t mix_offset) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
if (wave_buffer.buffer_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr std::array<int, 16> SIGNED_NIBBLES = {
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
|
||||
|
||||
constexpr std::size_t FRAME_LEN = 8;
|
||||
constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
|
||||
constexpr std::size_t SAMPLES_PER_FRAME = 14;
|
||||
|
||||
auto frame_header = dsp_state.context.header;
|
||||
s32 idx = (frame_header >> 4) & 0xf;
|
||||
s32 scale = frame_header & 0xf;
|
||||
s16 yn1 = dsp_state.context.yn1;
|
||||
s16 yn2 = dsp_state.context.yn2;
|
||||
|
||||
Codec::ADPCM_Coeff coeffs;
|
||||
memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
|
||||
sizeof(Codec::ADPCM_Coeff));
|
||||
|
||||
s32 coef1 = coeffs[idx * 2];
|
||||
s32 coef2 = coeffs[idx * 2 + 1];
|
||||
|
||||
const auto samples_remaining =
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
|
||||
const auto samples_processed = std::min(sample_count, samples_remaining);
|
||||
const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
|
||||
|
||||
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
|
||||
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
|
||||
samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
|
||||
|
||||
const auto decode_sample = [&](const int nibble) -> s16 {
|
||||
const int xn = nibble * (1 << scale);
|
||||
// We first transform everything into 11 bit fixed point, perform the second order
|
||||
// digital filter, then transform back.
|
||||
// 0x400 == 0.5 in 11 bit fixed point.
|
||||
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
||||
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
|
||||
// Clamp to output range.
|
||||
val = std::clamp<s32>(val, -32768, 32767);
|
||||
// Advance output feedback.
|
||||
yn2 = yn1;
|
||||
yn1 = val;
|
||||
return static_cast<s16>(val);
|
||||
};
|
||||
|
||||
std::size_t buffer_offset{};
|
||||
std::vector<u8> buffer(
|
||||
std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
|
||||
memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
|
||||
buffer.size());
|
||||
std::size_t cur_mix_offset = mix_offset;
|
||||
|
||||
auto remaining_samples = samples_processed;
|
||||
while (remaining_samples > 0) {
|
||||
if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
|
||||
// Read header
|
||||
frame_header = buffer[buffer_offset++];
|
||||
idx = (frame_header >> 4) & 0xf;
|
||||
scale = frame_header & 0xf;
|
||||
coef1 = coeffs[idx * 2];
|
||||
coef2 = coeffs[idx * 2 + 1];
|
||||
position_in_frame += 2;
|
||||
|
||||
// Decode entire frame
|
||||
if (remaining_samples >= SAMPLES_PER_FRAME) {
|
||||
for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
|
||||
|
||||
// Sample 1
|
||||
const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
|
||||
const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
|
||||
const s16 sample_1 = decode_sample(s0);
|
||||
const s16 sample_2 = decode_sample(s1);
|
||||
sample_buffer[cur_mix_offset++] = sample_1;
|
||||
sample_buffer[cur_mix_offset++] = sample_2;
|
||||
}
|
||||
remaining_samples -= SAMPLES_PER_FRAME;
|
||||
position_in_frame += SAMPLES_PER_FRAME;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Decode mid frame
|
||||
s32 current_nibble = buffer[buffer_offset];
|
||||
if (position_in_frame++ & 0x1) {
|
||||
current_nibble &= 0xf;
|
||||
buffer_offset++;
|
||||
} else {
|
||||
current_nibble >>= 4;
|
||||
}
|
||||
const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
|
||||
sample_buffer[cur_mix_offset++] = sample;
|
||||
remaining_samples--;
|
||||
}
|
||||
|
||||
dsp_state.context.header = frame_header;
|
||||
dsp_state.context.yn1 = yn1;
|
||||
dsp_state.context.yn2 = yn2;
|
||||
|
||||
return samples_processed;
|
||||
}
|
||||
|
||||
s32* CommandGenerator::GetMixBuffer(std::size_t index) {
|
||||
return mix_buffer.data() + (index * worker_params.sample_count);
|
||||
}
|
||||
|
||||
const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
|
||||
return mix_buffer.data() + (index * worker_params.sample_count);
|
||||
}
|
||||
|
||||
std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
|
||||
return worker_params.mix_buffer_count + channel;
|
||||
}
|
||||
|
||||
std::size_t CommandGenerator::GetTotalMixBufferCount() const {
|
||||
return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
|
||||
}
|
||||
|
||||
s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
|
||||
return GetMixBuffer(worker_params.mix_buffer_count + channel);
|
||||
}
|
||||
|
||||
const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
|
||||
return GetMixBuffer(worker_params.mix_buffer_count + channel);
|
||||
}
|
||||
|
||||
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
|
||||
VoiceState& dsp_state, s32 channel,
|
||||
s32 target_sample_rate, s32 sample_count,
|
||||
s32 node_id) {
|
||||
auto& in_params = voice_info.GetInParams();
|
||||
if (dumping_frame) {
|
||||
LOG_DEBUG(Audio,
|
||||
"(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
|
||||
"format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
|
||||
node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
|
||||
in_params.mix_id, in_params.splitter_info_id);
|
||||
}
|
||||
ASSERT_OR_EXECUTE(output != nullptr, { return; });
|
||||
|
||||
const auto resample_rate = static_cast<s32>(
|
||||
static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
|
||||
static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
|
||||
auto* output_base = output;
|
||||
if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto min_required_samples =
|
||||
std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
|
||||
if (min_required_samples >= sample_count) {
|
||||
min_required_samples = sample_count;
|
||||
}
|
||||
|
||||
std::size_t temp_mix_offset{};
|
||||
bool is_buffer_completed{false};
|
||||
auto samples_remaining = sample_count;
|
||||
while (samples_remaining > 0 && !is_buffer_completed) {
|
||||
const auto samples_to_output = std::min(samples_remaining, min_required_samples);
|
||||
const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
|
||||
|
||||
if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
|
||||
// Append sample histtory for resampler
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
|
||||
sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
|
||||
}
|
||||
temp_mix_offset += 4;
|
||||
}
|
||||
|
||||
s32 samples_read{};
|
||||
while (samples_read < samples_to_read) {
|
||||
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
|
||||
// No more data can be read
|
||||
if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
|
||||
is_buffer_completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
|
||||
wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
|
||||
// TODO(ogniK): ADPCM loop context
|
||||
}
|
||||
|
||||
s32 samples_decoded{0};
|
||||
switch (in_params.sample_format) {
|
||||
case SampleFormat::Pcm16:
|
||||
samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
|
||||
channel, temp_mix_offset);
|
||||
break;
|
||||
case SampleFormat::Adpcm:
|
||||
samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
|
||||
channel, temp_mix_offset);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
|
||||
}
|
||||
|
||||
temp_mix_offset += samples_decoded;
|
||||
samples_read += samples_decoded;
|
||||
dsp_state.offset += samples_decoded;
|
||||
dsp_state.played_sample_count += samples_decoded;
|
||||
|
||||
if (dsp_state.offset >=
|
||||
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
|
||||
samples_decoded == 0) {
|
||||
// Reset our sample offset
|
||||
dsp_state.offset = 0;
|
||||
if (wave_buffer.is_looping) {
|
||||
if (samples_decoded == 0) {
|
||||
// End of our buffer
|
||||
is_buffer_completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
|
||||
dsp_state.played_sample_count = 0;
|
||||
}
|
||||
} else {
|
||||
|
||||
// Update our wave buffer states
|
||||
dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
|
||||
dsp_state.wave_buffer_consumed++;
|
||||
dsp_state.wave_buffer_index =
|
||||
(dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
|
||||
if (wave_buffer.end_of_stream) {
|
||||
dsp_state.played_sample_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
|
||||
// No need to resample
|
||||
std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
|
||||
} else {
|
||||
std::fill(sample_buffer.begin() + temp_mix_offset,
|
||||
sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
|
||||
0);
|
||||
AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
|
||||
samples_to_output);
|
||||
// Resample
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
|
||||
dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
|
||||
}
|
||||
}
|
||||
output += samples_to_output;
|
||||
samples_remaining -= samples_to_output;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
class MixContext;
|
||||
class SplitterContext;
|
||||
class ServerSplitterDestinationData;
|
||||
class ServerMixInfo;
|
||||
class EffectContext;
|
||||
class EffectBase;
|
||||
struct AuxInfoDSP;
|
||||
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
|
||||
|
||||
class CommandGenerator {
|
||||
public:
|
||||
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
|
||||
VoiceContext& voice_context, MixContext& mix_context,
|
||||
SplitterContext& splitter_context, EffectContext& effect_context,
|
||||
Core::Memory::Memory& memory);
|
||||
~CommandGenerator();
|
||||
|
||||
void ClearMixBuffers();
|
||||
void GenerateVoiceCommands();
|
||||
void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
|
||||
void GenerateSubMixCommands();
|
||||
void GenerateFinalMixCommands();
|
||||
void PreCommand();
|
||||
void PostCommand();
|
||||
|
||||
s32* GetChannelMixBuffer(s32 channel);
|
||||
const s32* GetChannelMixBuffer(s32 channel) const;
|
||||
s32* GetMixBuffer(std::size_t index);
|
||||
const s32* GetMixBuffer(std::size_t index) const;
|
||||
std::size_t GetMixChannelBufferOffset(s32 channel) const;
|
||||
|
||||
std::size_t GetTotalMixBufferCount() const;
|
||||
|
||||
private:
|
||||
void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
|
||||
void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
|
||||
s32 mix_buffer_count, s32 channel);
|
||||
void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
|
||||
s32 node_id);
|
||||
void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
|
||||
const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
|
||||
s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
|
||||
s32 node_id);
|
||||
void GenerateSubMixCommand(ServerMixInfo& mix_info);
|
||||
void GenerateMixCommands(ServerMixInfo& mix_info);
|
||||
void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
|
||||
s32 node_id);
|
||||
void GenerateFinalMixCommand();
|
||||
void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
|
||||
std::array<s64, 2>& state, std::size_t input_offset,
|
||||
std::size_t output_offset, s32 sample_count, s32 node_id);
|
||||
void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset);
|
||||
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
|
||||
std::size_t mix_buffer_offset, s32 sample_rate);
|
||||
void GenerateEffectCommand(ServerMixInfo& mix_info);
|
||||
void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
|
||||
ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
|
||||
|
||||
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
|
||||
u32 sample_count, u32 write_offset, u32 write_count);
|
||||
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
|
||||
u32 sample_count, u32 read_offset, u32 read_count);
|
||||
|
||||
// DSP Code
|
||||
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
||||
s32 channel, std::size_t mix_offset);
|
||||
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
|
||||
s32 channel, std::size_t mix_offset);
|
||||
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
|
||||
s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
|
||||
|
||||
AudioCommon::AudioRendererParameter& worker_params;
|
||||
VoiceContext& voice_context;
|
||||
MixContext& mix_context;
|
||||
SplitterContext& splitter_context;
|
||||
EffectContext& effect_context;
|
||||
Core::Memory::Memory& memory;
|
||||
std::vector<s32> mix_buffer{};
|
||||
std::vector<s32> sample_buffer{};
|
||||
std::vector<s32> depop_buffer{};
|
||||
bool dumping_frame{false};
|
||||
};
|
||||
} // namespace AudioCore
|
||||
+62
-3
@@ -8,13 +8,30 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace AudioCommon {
|
||||
namespace Audren {
|
||||
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
|
||||
}
|
||||
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
|
||||
} // namespace Audren
|
||||
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
|
||||
constexpr std::size_t MAX_MIX_BUFFERS = 24;
|
||||
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
|
||||
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
|
||||
constexpr std::size_t MAX_WAVE_BUFFERS = 4;
|
||||
constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
|
||||
constexpr u32 STREAM_SAMPLE_RATE = 48000;
|
||||
constexpr u32 STREAM_NUM_CHANNELS = 6;
|
||||
constexpr s32 NO_SPLITTER = -1;
|
||||
constexpr s32 NO_MIX = 0x7fffffff;
|
||||
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
|
||||
constexpr s32 FINAL_MIX = 0;
|
||||
constexpr s32 NO_EFFECT_ORDER = -1;
|
||||
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
|
||||
// Any size checks seem to take the sample history into account
|
||||
// and our const ends up being 0x3f04, the 4 bytes are most
|
||||
// likely the sample history
|
||||
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
|
||||
|
||||
static constexpr u32 VersionFromRevision(u32_le rev) {
|
||||
// "REV7" -> 7
|
||||
@@ -45,4 +62,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
struct UpdateDataSizes {
|
||||
u32_le behavior{};
|
||||
u32_le memory_pool{};
|
||||
u32_le voice{};
|
||||
u32_le voice_channel_resource{};
|
||||
u32_le effect{};
|
||||
u32_le mixer{};
|
||||
u32_le sink{};
|
||||
u32_le performance{};
|
||||
u32_le splitter{};
|
||||
u32_le render_info{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
};
|
||||
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
u32_le revision{};
|
||||
UpdateDataSizes size{};
|
||||
u32_le total_size{};
|
||||
};
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
|
||||
|
||||
struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le submix_count;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
|
||||
} // namespace AudioCommon
|
||||
|
||||
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
|
||||
public:
|
||||
CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
||||
const std::string& name)
|
||||
: ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
|
||||
: ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
|
||||
num_channels} {
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = sample_rate;
|
||||
params.channels = num_channels;
|
||||
params.format = CUBEB_SAMPLE_S16NE;
|
||||
params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
|
||||
switch (num_channels) {
|
||||
case 1:
|
||||
params.layout = CUBEB_LAYOUT_MONO;
|
||||
break;
|
||||
case 2:
|
||||
params.layout = CUBEB_LAYOUT_STEREO;
|
||||
break;
|
||||
case 6:
|
||||
params.layout = CUBEB_LAYOUT_3F2_LFE;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 minimum_latency{};
|
||||
if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) {
|
||||
@@ -193,6 +203,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
|
||||
const std::size_t samples_to_write = num_channels * num_frames;
|
||||
std::size_t samples_written;
|
||||
|
||||
/*
|
||||
if (Settings::values.enable_audio_stretching.GetValue()) {
|
||||
const std::vector<s16> in{impl->queue.Pop()};
|
||||
const std::size_t num_in{in.size() / num_channels};
|
||||
@@ -207,7 +218,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
|
||||
}
|
||||
} else {
|
||||
samples_written = impl->queue.Pop(buffer, samples_to_write);
|
||||
}
|
||||
}*/
|
||||
samples_written = impl->queue.Pop(buffer, samples_to_write);
|
||||
|
||||
if (samples_written >= num_channels) {
|
||||
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "audio_core/effect_context.h"
|
||||
|
||||
namespace AudioCore {
|
||||
namespace {
|
||||
bool ValidChannelCountForEffect(s32 channel_count) {
|
||||
return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
|
||||
effects.reserve(effect_count);
|
||||
std::generate_n(std::back_inserter(effects), effect_count,
|
||||
[] { return std::make_unique<EffectStubbed>(); });
|
||||
}
|
||||
EffectContext::~EffectContext() = default;
|
||||
|
||||
std::size_t EffectContext::GetCount() const {
|
||||
return effect_count;
|
||||
}
|
||||
|
||||
EffectBase* EffectContext::GetInfo(std::size_t i) {
|
||||
return effects.at(i).get();
|
||||
}
|
||||
|
||||
EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
|
||||
switch (effect) {
|
||||
case EffectType::Invalid:
|
||||
effects[i] = std::make_unique<EffectStubbed>();
|
||||
break;
|
||||
case EffectType::BufferMixer:
|
||||
effects[i] = std::make_unique<EffectBufferMixer>();
|
||||
break;
|
||||
case EffectType::Aux:
|
||||
effects[i] = std::make_unique<EffectAuxInfo>();
|
||||
break;
|
||||
case EffectType::Delay:
|
||||
effects[i] = std::make_unique<EffectDelay>();
|
||||
break;
|
||||
case EffectType::Reverb:
|
||||
effects[i] = std::make_unique<EffectReverb>();
|
||||
break;
|
||||
case EffectType::I3dl2Reverb:
|
||||
effects[i] = std::make_unique<EffectI3dl2Reverb>();
|
||||
break;
|
||||
case EffectType::BiquadFilter:
|
||||
effects[i] = std::make_unique<EffectBiquadFilter>();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unimplemented effect {}", effect);
|
||||
effects[i] = std::make_unique<EffectStubbed>();
|
||||
}
|
||||
return GetInfo(i);
|
||||
}
|
||||
|
||||
const EffectBase* EffectContext::GetInfo(std::size_t i) const {
|
||||
return effects.at(i).get();
|
||||
}
|
||||
|
||||
EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
|
||||
EffectStubbed::~EffectStubbed() = default;
|
||||
|
||||
void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
|
||||
void EffectStubbed::UpdateForCommandGeneration() {}
|
||||
|
||||
EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
|
||||
EffectBase::~EffectBase() = default;
|
||||
|
||||
UsageState EffectBase::GetUsage() const {
|
||||
return usage;
|
||||
}
|
||||
|
||||
EffectType EffectBase::GetType() const {
|
||||
return effect_type;
|
||||
}
|
||||
|
||||
bool EffectBase::IsEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
s32 EffectBase::GetMixID() const {
|
||||
return mix_id;
|
||||
}
|
||||
|
||||
s32 EffectBase::GetProcessingOrder() const {
|
||||
return processing_order;
|
||||
}
|
||||
|
||||
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
|
||||
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
|
||||
|
||||
void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
|
||||
auto& internal_params = GetParams();
|
||||
const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
|
||||
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
|
||||
UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto last_status = internal_params.status;
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
internal_params = *reverb_params;
|
||||
if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
|
||||
internal_params.channel_count = internal_params.max_channels;
|
||||
}
|
||||
enabled = in_params.is_enabled;
|
||||
if (last_status != ParameterStatus::Updated) {
|
||||
internal_params.status = last_status;
|
||||
}
|
||||
|
||||
if (in_params.is_new || skipped) {
|
||||
usage = UsageState::Initialized;
|
||||
internal_params.status = ParameterStatus::Initialized;
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
|
||||
}
|
||||
}
|
||||
|
||||
void EffectI3dl2Reverb::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
GetParams().status = ParameterStatus::Updated;
|
||||
}
|
||||
|
||||
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
|
||||
EffectBiquadFilter::~EffectBiquadFilter() = default;
|
||||
|
||||
void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
|
||||
auto& internal_params = GetParams();
|
||||
const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
internal_params = *biquad_params;
|
||||
enabled = in_params.is_enabled;
|
||||
}
|
||||
|
||||
void EffectBiquadFilter::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
GetParams().status = ParameterStatus::Updated;
|
||||
}
|
||||
|
||||
EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
|
||||
EffectAuxInfo::~EffectAuxInfo() = default;
|
||||
|
||||
void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
|
||||
const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
GetParams() = *aux_params;
|
||||
enabled = in_params.is_enabled;
|
||||
|
||||
if (in_params.is_new || skipped) {
|
||||
skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
|
||||
if (skipped) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There's two AuxInfos which are an identical size, the first one is managed by the cpu,
|
||||
// the second is managed by the dsp. All we care about is managing the DSP one
|
||||
send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
|
||||
send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
|
||||
|
||||
recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
|
||||
recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
void EffectAuxInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
const VAddr EffectAuxInfo::GetSendInfo() const {
|
||||
return send_info;
|
||||
}
|
||||
|
||||
const VAddr EffectAuxInfo::GetSendBuffer() const {
|
||||
return send_buffer;
|
||||
}
|
||||
|
||||
const VAddr EffectAuxInfo::GetRecvInfo() const {
|
||||
return recv_info;
|
||||
}
|
||||
|
||||
const VAddr EffectAuxInfo::GetRecvBuffer() const {
|
||||
return recv_buffer;
|
||||
}
|
||||
|
||||
EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
|
||||
EffectDelay::~EffectDelay() = default;
|
||||
|
||||
void EffectDelay::Update(EffectInfo::InParams& in_params) {
|
||||
const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
|
||||
auto& internal_params = GetParams();
|
||||
if (!ValidChannelCountForEffect(delay_params->max_channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto last_status = internal_params.status;
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
internal_params = *delay_params;
|
||||
if (!ValidChannelCountForEffect(delay_params->channels)) {
|
||||
internal_params.channels = internal_params.max_channels;
|
||||
}
|
||||
enabled = in_params.is_enabled;
|
||||
|
||||
if (last_status != ParameterStatus::Updated) {
|
||||
internal_params.status = last_status;
|
||||
}
|
||||
|
||||
if (in_params.is_new || skipped) {
|
||||
usage = UsageState::Initialized;
|
||||
internal_params.status = ParameterStatus::Initialized;
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
|
||||
}
|
||||
}
|
||||
|
||||
void EffectDelay::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
GetParams().status = ParameterStatus::Updated;
|
||||
}
|
||||
|
||||
EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
|
||||
EffectBufferMixer::~EffectBufferMixer() = default;
|
||||
|
||||
void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
|
||||
enabled = in_params.is_enabled;
|
||||
}
|
||||
|
||||
void EffectBufferMixer::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
|
||||
EffectReverb::~EffectReverb() = default;
|
||||
|
||||
void EffectReverb::Update(EffectInfo::InParams& in_params) {
|
||||
const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
|
||||
auto& internal_params = GetParams();
|
||||
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto last_status = internal_params.status;
|
||||
mix_id = in_params.mix_id;
|
||||
processing_order = in_params.processing_order;
|
||||
internal_params = *reverb_params;
|
||||
if (!ValidChannelCountForEffect(reverb_params->channels)) {
|
||||
internal_params.channels = internal_params.max_channels;
|
||||
}
|
||||
enabled = in_params.is_enabled;
|
||||
|
||||
if (last_status != ParameterStatus::Updated) {
|
||||
internal_params.status = last_status;
|
||||
}
|
||||
|
||||
if (in_params.is_new || skipped) {
|
||||
usage = UsageState::Initialized;
|
||||
internal_params.status = ParameterStatus::Initialized;
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
|
||||
}
|
||||
}
|
||||
|
||||
void EffectReverb::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage = UsageState::Running;
|
||||
} else {
|
||||
usage = UsageState::Stopped;
|
||||
}
|
||||
GetParams().status = ParameterStatus::Updated;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,322 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "audio_core/common.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace AudioCore {
|
||||
enum class EffectType : u8 {
|
||||
Invalid = 0,
|
||||
BufferMixer = 1,
|
||||
Aux = 2,
|
||||
Delay = 3,
|
||||
Reverb = 4,
|
||||
I3dl2Reverb = 5,
|
||||
BiquadFilter = 6,
|
||||
};
|
||||
|
||||
enum class UsageStatus : u8 {
|
||||
Invalid = 0,
|
||||
New = 1,
|
||||
Initialized = 2,
|
||||
Used = 3,
|
||||
Removed = 4,
|
||||
};
|
||||
|
||||
enum class UsageState {
|
||||
Invalid = 0,
|
||||
Initialized = 1,
|
||||
Running = 2,
|
||||
Stopped = 3,
|
||||
};
|
||||
|
||||
enum class ParameterStatus : u8 {
|
||||
Initialized = 0,
|
||||
Updating = 1,
|
||||
Updated = 2,
|
||||
};
|
||||
|
||||
struct BufferMixerParams {
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
|
||||
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
|
||||
s32_le count{};
|
||||
};
|
||||
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
|
||||
|
||||
struct AuxInfoDSP {
|
||||
u32_le read_offset{};
|
||||
u32_le write_offset{};
|
||||
u32_le remaining{};
|
||||
INSERT_PADDING_WORDS(13);
|
||||
};
|
||||
static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
|
||||
|
||||
struct AuxInfo {
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
|
||||
u32_le count{};
|
||||
s32_le sample_rate{};
|
||||
s32_le sample_count{};
|
||||
s32_le mix_buffer_count{};
|
||||
u64_le send_buffer_info{};
|
||||
u64_le send_buffer_base{};
|
||||
|
||||
u64_le return_buffer_info{};
|
||||
u64_le return_buffer_base{};
|
||||
};
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
|
||||
|
||||
struct I3dl2ReverbParams {
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
|
||||
u16_le max_channels{};
|
||||
u16_le channel_count{};
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u32_le sample_rate{};
|
||||
f32 room_hf{};
|
||||
f32 hf_reference{};
|
||||
f32 decay_time{};
|
||||
f32 hf_decay_ratio{};
|
||||
f32 room{};
|
||||
f32 reflection{};
|
||||
f32 reverb{};
|
||||
f32 diffusion{};
|
||||
f32 reflection_delay{};
|
||||
f32 reverb_delay{};
|
||||
f32 density{};
|
||||
f32 dry_gain{};
|
||||
ParameterStatus status{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
|
||||
|
||||
struct BiquadFilterParams {
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
|
||||
std::array<s16_le, 3> numerator;
|
||||
std::array<s16_le, 2> denominator;
|
||||
s8 channel_count{};
|
||||
ParameterStatus status{};
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
|
||||
|
||||
struct DelayParams {
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
|
||||
u16_le max_channels{};
|
||||
u16_le channels{};
|
||||
s32_le max_delay{};
|
||||
s32_le delay{};
|
||||
s32_le sample_rate{};
|
||||
s32_le gain{};
|
||||
s32_le feedback_gain{};
|
||||
s32_le out_gain{};
|
||||
s32_le dry_gain{};
|
||||
s32_le channel_spread{};
|
||||
s32_le low_pass{};
|
||||
ParameterStatus status{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
|
||||
|
||||
struct ReverbParams {
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
|
||||
u16_le max_channels{};
|
||||
u16_le channels{};
|
||||
s32_le sample_rate{};
|
||||
s32_le mode0{};
|
||||
s32_le mode0_gain{};
|
||||
s32_le pre_delay{};
|
||||
s32_le mode1{};
|
||||
s32_le mode1_gain{};
|
||||
s32_le decay{};
|
||||
s32_le hf_decay_ratio{};
|
||||
s32_le coloration{};
|
||||
s32_le reverb_gain{};
|
||||
s32_le out_gain{};
|
||||
s32_le dry_gain{};
|
||||
ParameterStatus status{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
|
||||
|
||||
class EffectInfo {
|
||||
public:
|
||||
struct InParams {
|
||||
EffectType type{};
|
||||
u8 is_new{};
|
||||
u8 is_enabled{};
|
||||
INSERT_PADDING_BYTES(1);
|
||||
s32_le mix_id{};
|
||||
u64_le buffer_address{};
|
||||
u64_le buffer_size{};
|
||||
s32_le processing_order{};
|
||||
INSERT_PADDING_BYTES(4);
|
||||
union {
|
||||
std::array<u8, 0xa0> raw;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size");
|
||||
|
||||
struct OutParams {
|
||||
UsageStatus status{};
|
||||
INSERT_PADDING_BYTES(15);
|
||||
};
|
||||
static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
|
||||
};
|
||||
|
||||
struct AuxAddress {
|
||||
VAddr send_dsp_info{};
|
||||
VAddr send_buffer_base{};
|
||||
VAddr return_dsp_info{};
|
||||
VAddr return_buffer_base{};
|
||||
};
|
||||
|
||||
class EffectBase {
|
||||
public:
|
||||
EffectBase(EffectType effect_type);
|
||||
~EffectBase();
|
||||
|
||||
virtual void Update(EffectInfo::InParams& in_params) = 0;
|
||||
virtual void UpdateForCommandGeneration() = 0;
|
||||
UsageState GetUsage() const;
|
||||
EffectType GetType() const;
|
||||
bool IsEnabled() const;
|
||||
s32 GetMixID() const;
|
||||
s32 GetProcessingOrder() const;
|
||||
|
||||
protected:
|
||||
UsageState usage{UsageState::Invalid};
|
||||
EffectType effect_type{};
|
||||
s32 mix_id{};
|
||||
s32 processing_order{};
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class EffectGeneric : public EffectBase {
|
||||
public:
|
||||
EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {}
|
||||
~EffectGeneric() = default;
|
||||
|
||||
T& GetParams() {
|
||||
return internal_params;
|
||||
}
|
||||
|
||||
const I3dl2ReverbParams& GetParams() const {
|
||||
return internal_params;
|
||||
}
|
||||
|
||||
private:
|
||||
T internal_params{};
|
||||
};
|
||||
|
||||
class EffectStubbed : public EffectBase {
|
||||
public:
|
||||
explicit EffectStubbed();
|
||||
~EffectStubbed();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
};
|
||||
|
||||
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
|
||||
public:
|
||||
explicit EffectI3dl2Reverb();
|
||||
~EffectI3dl2Reverb();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
private:
|
||||
bool skipped = false;
|
||||
};
|
||||
|
||||
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
|
||||
public:
|
||||
explicit EffectBiquadFilter();
|
||||
~EffectBiquadFilter();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
};
|
||||
|
||||
class EffectAuxInfo : public EffectGeneric<AuxInfo> {
|
||||
public:
|
||||
explicit EffectAuxInfo();
|
||||
~EffectAuxInfo();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
const VAddr GetSendInfo() const;
|
||||
const VAddr GetSendBuffer() const;
|
||||
const VAddr GetRecvInfo() const;
|
||||
const VAddr GetRecvBuffer() const;
|
||||
|
||||
private:
|
||||
VAddr send_info{};
|
||||
VAddr send_buffer{};
|
||||
VAddr recv_info{};
|
||||
VAddr recv_buffer{};
|
||||
bool skipped = false;
|
||||
AuxAddress addresses{};
|
||||
};
|
||||
|
||||
class EffectDelay : public EffectGeneric<DelayParams> {
|
||||
public:
|
||||
explicit EffectDelay();
|
||||
~EffectDelay();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
private:
|
||||
bool skipped = false;
|
||||
};
|
||||
|
||||
class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
|
||||
public:
|
||||
explicit EffectBufferMixer();
|
||||
~EffectBufferMixer();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
};
|
||||
|
||||
class EffectReverb : public EffectGeneric<ReverbParams> {
|
||||
public:
|
||||
explicit EffectReverb();
|
||||
~EffectReverb();
|
||||
|
||||
void Update(EffectInfo::InParams& in_params) override;
|
||||
void UpdateForCommandGeneration() override;
|
||||
|
||||
private:
|
||||
bool skipped = false;
|
||||
};
|
||||
|
||||
class EffectContext {
|
||||
public:
|
||||
explicit EffectContext(std::size_t effect_count);
|
||||
~EffectContext();
|
||||
|
||||
std::size_t GetCount() const;
|
||||
EffectBase* GetInfo(std::size_t i);
|
||||
EffectBase* RetargetEffect(std::size_t i, EffectType effect);
|
||||
const EffectBase* GetInfo(std::size_t i) const;
|
||||
|
||||
private:
|
||||
std::size_t effect_count{};
|
||||
std::vector<std::unique_ptr<EffectBase>> effects;
|
||||
};
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,517 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/info_updater.h"
|
||||
#include "audio_core/memory_pool.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/sink_context.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
|
||||
BehaviorInfo& behavior_info)
|
||||
: in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
|
||||
ASSERT(
|
||||
AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
|
||||
std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
|
||||
output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
|
||||
}
|
||||
|
||||
InfoUpdater::~InfoUpdater() = default;
|
||||
|
||||
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
|
||||
if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
|
||||
LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
sizeof(BehaviorInfo::InParams), input_header.size.behavior);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
|
||||
sizeof(BehaviorInfo::InParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
BehaviorInfo::InParams behavior_in{};
|
||||
std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
|
||||
input_offset += sizeof(BehaviorInfo::InParams);
|
||||
|
||||
// Make sure it's an audio revision we can actually support
|
||||
if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
|
||||
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that our behavior info revision matches the input
|
||||
if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
|
||||
LOG_ERROR(Audio,
|
||||
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
|
||||
in_behavior_info.GetUserRevision(), behavior_in.revision);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update behavior info flags
|
||||
in_behavior_info.ClearError();
|
||||
in_behavior_info.UpdateFlags(behavior_in.flags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
|
||||
const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled();
|
||||
const auto memory_pool_count = memory_pool_info.size();
|
||||
const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
|
||||
const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
|
||||
|
||||
if (input_header.size.memory_pool != total_memory_pool_in) {
|
||||
LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
total_memory_pool_in, input_header.size.memory_pool);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
|
||||
std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
|
||||
|
||||
std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
|
||||
input_offset += total_memory_pool_in;
|
||||
|
||||
// Update our memory pools
|
||||
for (std::size_t i = 0; i < memory_pool_count; i++) {
|
||||
if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
|
||||
LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
|
||||
sizeof(BehaviorInfo::InParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
|
||||
output_offset += total_memory_pool_out;
|
||||
output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
|
||||
const auto voice_count = voice_context.GetVoiceCount();
|
||||
const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
|
||||
std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
|
||||
|
||||
if (input_header.size.voice_channel_resource != voice_size) {
|
||||
LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
voice_size, input_header.size.voice_channel_resource);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
|
||||
input_offset += voice_size;
|
||||
|
||||
// Update our channel resources
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
// Grab our channel resource
|
||||
auto& resource = voice_context.GetChannelResource(i);
|
||||
resource.Update(resources_in[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
|
||||
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
|
||||
VAddr audio_codec_dsp_addr) {
|
||||
const auto voice_count = voice_context.GetVoiceCount();
|
||||
std::vector<VoiceInfo::InParams> voice_in(voice_count);
|
||||
std::vector<VoiceInfo::OutParams> voice_out(voice_count);
|
||||
|
||||
const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
|
||||
const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
|
||||
|
||||
if (input_header.size.voice != voice_in_size) {
|
||||
LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
voice_in_size, input_header.size.voice);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
|
||||
input_offset += voice_in_size;
|
||||
|
||||
// Set all voices to not be in use
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
voice_context.GetInfo(i).GetInParams().in_use = false;
|
||||
}
|
||||
|
||||
// Update our voices
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
auto& in_params = voice_in[i];
|
||||
const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
|
||||
// Skip if it's not currently in use
|
||||
if (!in_params.is_in_use) {
|
||||
continue;
|
||||
}
|
||||
// Voice states for each channel
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
|
||||
ASSERT(in_params.id < voice_count);
|
||||
|
||||
// Grab our current voice info
|
||||
auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
|
||||
|
||||
ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
|
||||
|
||||
// Get all our channel voice states
|
||||
for (std::size_t channel = 0; channel < channel_count; channel++) {
|
||||
voice_states[channel] =
|
||||
&voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
|
||||
}
|
||||
|
||||
if (in_params.is_new) {
|
||||
// Default our values for our voice
|
||||
voice_info.Initialize();
|
||||
if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Zero out our voice states
|
||||
for (std::size_t channel = 0; channel < channel_count; channel++) {
|
||||
std::memset(voice_states[channel], 0, sizeof(VoiceState));
|
||||
}
|
||||
}
|
||||
|
||||
// Update our voice
|
||||
voice_info.UpdateParameters(in_params, behavior_info);
|
||||
// TODO(ogniK): Handle mapping errors with behavior info based on in params response
|
||||
|
||||
// Update our wave buffers
|
||||
voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
|
||||
voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
|
||||
output_offset += voice_out_size;
|
||||
output_header.size.voice = static_cast<u32>(voice_out_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
|
||||
const auto effect_count = effect_context.GetCount();
|
||||
std::vector<EffectInfo::InParams> effect_in(effect_count);
|
||||
std::vector<EffectInfo::OutParams> effect_out(effect_count);
|
||||
|
||||
const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
|
||||
const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
|
||||
|
||||
if (input_header.size.effect != total_effect_in) {
|
||||
LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
total_effect_in, input_header.size.effect);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
|
||||
input_offset += total_effect_in;
|
||||
|
||||
// Update effects
|
||||
for (std::size_t i = 0; i < effect_count; i++) {
|
||||
auto* info = effect_context.GetInfo(i);
|
||||
if (effect_in[i].type != info->GetType()) {
|
||||
info = effect_context.RetargetEffect(i, effect_in[i].type);
|
||||
}
|
||||
|
||||
info->Update(effect_in[i]);
|
||||
|
||||
if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
|
||||
info->GetUsage() == UsageState::Stopped) {
|
||||
effect_out[i].status = UsageStatus::Removed;
|
||||
} else {
|
||||
effect_out[i].status = UsageStatus::Used;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
|
||||
output_offset += total_effect_out;
|
||||
output_header.size.effect = static_cast<u32>(total_effect_out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
|
||||
std::size_t start_offset = input_offset;
|
||||
std::size_t bytes_read{};
|
||||
// Update splitter context
|
||||
if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
|
||||
LOG_ERROR(Audio, "Failed to update splitter context!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto consumed = input_offset - start_offset;
|
||||
|
||||
if (input_header.size.splitter != consumed) {
|
||||
LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
bytes_read, input_header.size.splitter);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
|
||||
SplitterContext& splitter_context,
|
||||
EffectContext& effect_context) {
|
||||
std::vector<MixInfo::InParams> mix_in_params;
|
||||
|
||||
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
|
||||
// If we're not dirty, get ALL mix in parameters
|
||||
const auto context_mix_count = mix_context.GetCount();
|
||||
const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
|
||||
if (input_header.size.mixer != total_mix_in) {
|
||||
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
total_mix_in, input_header.size.mixer);
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
mix_in_params.resize(context_mix_count);
|
||||
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
|
||||
|
||||
input_offset += total_mix_in;
|
||||
} else {
|
||||
// Only update the "dirty" mixes
|
||||
MixInfo::DirtyHeader dirty_header{};
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
|
||||
sizeof(MixInfo::DirtyHeader))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
|
||||
input_offset += sizeof(MixInfo::DirtyHeader);
|
||||
|
||||
const auto total_mix_in =
|
||||
dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
|
||||
|
||||
if (input_header.size.mixer != total_mix_in) {
|
||||
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
total_mix_in, input_header.size.mixer);
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (dirty_header.mixer_count != 0) {
|
||||
mix_in_params.resize(dirty_header.mixer_count);
|
||||
std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
|
||||
mix_in_params.size() * sizeof(MixInfo::InParams));
|
||||
input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
|
||||
}
|
||||
}
|
||||
|
||||
// Get our total input count
|
||||
const auto mix_count = mix_in_params.size();
|
||||
|
||||
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
|
||||
// Only verify our buffer count if we're not dirty
|
||||
std::size_t total_buffer_count{};
|
||||
for (std::size_t i = 0; i < mix_count; i++) {
|
||||
const auto& in = mix_in_params[i];
|
||||
total_buffer_count += in.buffer_count;
|
||||
if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX &&
|
||||
in.mix_id != AudioCommon::FINAL_MIX) {
|
||||
LOG_ERROR(
|
||||
Audio,
|
||||
"Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
|
||||
in.mix_id, in.dest_mix_id, mix_buffer_count);
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_buffer_count > mix_buffer_count) {
|
||||
LOG_ERROR(Audio,
|
||||
"Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
|
||||
mix_buffer_count, total_buffer_count);
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
if (mix_buffer_count == 0) {
|
||||
LOG_ERROR(Audio, "No mix buffers!");
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
bool should_sort = false;
|
||||
for (std::size_t i = 0; i < mix_count; i++) {
|
||||
const auto& mix_in = mix_in_params[i];
|
||||
std::size_t target_mix{};
|
||||
if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
|
||||
target_mix = mix_in.mix_id;
|
||||
} else {
|
||||
// Non dirty supported games just use i instead of the actual mix_id
|
||||
target_mix = i;
|
||||
}
|
||||
auto& mix_info = mix_context.GetInfo(target_mix);
|
||||
auto& mix_info_params = mix_info.GetInParams();
|
||||
if (mix_info_params.in_use != mix_in.in_use) {
|
||||
mix_info_params.in_use = mix_in.in_use;
|
||||
mix_info.ResetEffectProcessingOrder();
|
||||
should_sort = true;
|
||||
}
|
||||
|
||||
if (mix_in.in_use) {
|
||||
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
|
||||
splitter_context, effect_context);
|
||||
}
|
||||
}
|
||||
|
||||
if (should_sort && behavior_info.IsSplitterSupported()) {
|
||||
// Sort our splitter data
|
||||
if (!mix_context.TsortInfo(splitter_context)) {
|
||||
return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ogniK): Sort when splitter is suppoorted
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
|
||||
const auto sink_count = sink_context.GetCount();
|
||||
std::vector<SinkInfo::InParams> sink_in_params(sink_count);
|
||||
const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
|
||||
|
||||
if (input_header.size.sink != total_sink_in) {
|
||||
LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
|
||||
total_sink_in, input_header.size.effect);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
|
||||
input_offset += total_sink_in;
|
||||
|
||||
// TODO(ogniK): Properly update sinks
|
||||
if (!sink_in_params.empty()) {
|
||||
sink_context.UpdateMainSink(sink_in_params[0]);
|
||||
}
|
||||
|
||||
output_header.size.sink = static_cast<u32>(0x20 * sink_count);
|
||||
output_offset += 0x20 * sink_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdatePerformanceBuffer() {
|
||||
output_header.size.performance = 0x10;
|
||||
output_offset += 0x10;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
|
||||
const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
|
||||
BehaviorInfo::OutParams behavior_info_out{};
|
||||
behavior_info.CopyErrorInfo(behavior_info_out);
|
||||
|
||||
std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
|
||||
output_offset += total_beahvior_info_out;
|
||||
output_header.size.behavior = total_beahvior_info_out;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct RendererInfo {
|
||||
u64_le elasped_frame_count{};
|
||||
INSERT_PADDING_WORDS(2);
|
||||
};
|
||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
|
||||
|
||||
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
|
||||
const auto total_renderer_info_out = sizeof(RendererInfo);
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
RendererInfo out{};
|
||||
out.elasped_frame_count = elapsed_frame_count;
|
||||
std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
|
||||
output_offset += total_renderer_info_out;
|
||||
output_header.size.render_info = total_renderer_info_out;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::CheckConsumedSize() const {
|
||||
if (output_offset != out_params.size()) {
|
||||
LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
|
||||
output_offset, out_params.size(), out_params.size() - output_offset);
|
||||
return false;
|
||||
}
|
||||
/*if (input_offset != in_params.size()) {
|
||||
LOG_ERROR(Audio, "Input is not consumed!");
|
||||
return false;
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InfoUpdater::WriteOutputHeader() {
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
|
||||
sizeof(AudioCommon::UpdateDataHeader))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
|
||||
const auto& sz = output_header.size;
|
||||
output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
|
||||
sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
|
||||
sz.performance + sz.splitter + sz.render_info;
|
||||
|
||||
std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "audio_core/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class BehaviorInfo;
|
||||
class ServerMemoryPoolInfo;
|
||||
class VoiceContext;
|
||||
class EffectContext;
|
||||
class MixContext;
|
||||
class SinkContext;
|
||||
class SplitterContext;
|
||||
|
||||
class InfoUpdater {
|
||||
public:
|
||||
// TODO(ogniK): Pass process handle when we support it
|
||||
InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
|
||||
BehaviorInfo& behavior_info);
|
||||
~InfoUpdater();
|
||||
|
||||
bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
|
||||
bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
|
||||
bool UpdateVoiceChannelResources(VoiceContext& voice_context);
|
||||
bool UpdateVoices(VoiceContext& voice_context,
|
||||
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
|
||||
VAddr audio_codec_dsp_addr);
|
||||
bool UpdateEffects(EffectContext& effect_context, bool is_active);
|
||||
bool UpdateSplitterInfo(SplitterContext& splitter_context);
|
||||
ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
|
||||
SplitterContext& splitter_context, EffectContext& effect_context);
|
||||
bool UpdateSinks(SinkContext& sink_context);
|
||||
bool UpdatePerformanceBuffer();
|
||||
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
|
||||
bool UpdateRendererInfo(std::size_t elapsed_frame_count);
|
||||
bool CheckConsumedSize() const;
|
||||
|
||||
bool WriteOutputHeader();
|
||||
|
||||
private:
|
||||
const std::vector<u8>& in_params;
|
||||
std::vector<u8>& out_params;
|
||||
BehaviorInfo& behavior_info;
|
||||
|
||||
AudioCommon::UpdateDataHeader input_header{};
|
||||
AudioCommon::UpdateDataHeader output_header{};
|
||||
|
||||
std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
|
||||
std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/memory_pool.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
|
||||
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
|
||||
bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
|
||||
ServerMemoryPoolInfo::OutParams& out_params) {
|
||||
// Our state does not need to be changed
|
||||
if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
|
||||
in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Address or size is null
|
||||
if (in_params.address == 0 || in_params.size == 0) {
|
||||
LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
|
||||
in_params.address, in_params.size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Address or size is not aligned
|
||||
if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
|
||||
LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
|
||||
in_params.address, in_params.size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
|
||||
cpu_address = in_params.address;
|
||||
size = in_params.size;
|
||||
used = true;
|
||||
out_params.state = ServerMemoryPoolInfo::State::Attached;
|
||||
} else {
|
||||
// Unexpected address
|
||||
if (cpu_address != in_params.address) {
|
||||
LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
|
||||
cpu_address, in_params.address);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size != in_params.size) {
|
||||
LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
|
||||
in_params.size);
|
||||
return false;
|
||||
}
|
||||
|
||||
cpu_address = 0;
|
||||
size = 0;
|
||||
used = false;
|
||||
out_params.state = ServerMemoryPoolInfo::State::Detached;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class ServerMemoryPoolInfo {
|
||||
public:
|
||||
ServerMemoryPoolInfo();
|
||||
~ServerMemoryPoolInfo();
|
||||
|
||||
enum class State : u32_le {
|
||||
Invalid = 0x0,
|
||||
Aquired = 0x1,
|
||||
RequestDetach = 0x2,
|
||||
Detached = 0x3,
|
||||
RequestAttach = 0x4,
|
||||
Attached = 0x5,
|
||||
Released = 0x6,
|
||||
};
|
||||
|
||||
struct InParams {
|
||||
u64_le address{};
|
||||
u64_le size{};
|
||||
ServerMemoryPoolInfo::State state{};
|
||||
INSERT_PADDING_WORDS(3);
|
||||
};
|
||||
static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
|
||||
|
||||
struct OutParams {
|
||||
ServerMemoryPoolInfo::State state{};
|
||||
INSERT_PADDING_WORDS(3);
|
||||
};
|
||||
static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
|
||||
|
||||
bool Update(const ServerMemoryPoolInfo::InParams& in_params,
|
||||
ServerMemoryPoolInfo::OutParams& out_params);
|
||||
|
||||
private:
|
||||
// There's another entry here which is the DSP address, however since we're not talking to the
|
||||
// DSP we can just use the same address provided by the guest without needing to remap
|
||||
u64_le cpu_address{};
|
||||
u64_le size{};
|
||||
bool used{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/effect_context.h"
|
||||
#include "audio_core/mix_context.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
|
||||
namespace AudioCore {
|
||||
MixContext::MixContext() = default;
|
||||
MixContext::~MixContext() = default;
|
||||
|
||||
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
|
||||
std::size_t effect_count) {
|
||||
info_count = mix_count;
|
||||
infos.resize(info_count);
|
||||
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
|
||||
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
|
||||
sorted_info.reserve(infos.size());
|
||||
for (auto& info : infos) {
|
||||
sorted_info.push_back(&info);
|
||||
}
|
||||
|
||||
for (auto& info : infos) {
|
||||
info.SetEffectCount(effect_count);
|
||||
}
|
||||
|
||||
// Only initialize our edge matrix and node states if splitters are supported
|
||||
if (behavior_info.IsSplitterSupported()) {
|
||||
node_states.Initialize(mix_count);
|
||||
edge_matrix.Initialize(mix_count);
|
||||
}
|
||||
}
|
||||
|
||||
void MixContext::UpdateDistancesFromFinalMix() {
|
||||
// Set all distances to be invalid
|
||||
for (std::size_t i = 0; i < info_count; i++) {
|
||||
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < info_count; i++) {
|
||||
auto& info = GetInfo(i);
|
||||
auto& in_params = info.GetInParams();
|
||||
// Populate our sorted info
|
||||
sorted_info[i] = &info;
|
||||
|
||||
if (!in_params.in_use) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto mix_id = in_params.mix_id;
|
||||
// Needs to be referenced out of scope
|
||||
s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
|
||||
for (; distance_to_final_mix < info_count; distance_to_final_mix++) {
|
||||
if (mix_id == AudioCommon::FINAL_MIX) {
|
||||
// If we're at the final mix, we're done
|
||||
break;
|
||||
} else if (mix_id == AudioCommon::NO_MIX) {
|
||||
// If we have no more mix ids, we're done
|
||||
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
|
||||
break;
|
||||
} else {
|
||||
const auto& dest_mix = GetInfo(mix_id);
|
||||
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
|
||||
|
||||
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
|
||||
// If our current mix isn't pointing to a final mix, follow through
|
||||
mix_id = dest_mix.GetInParams().dest_mix_id;
|
||||
} else {
|
||||
// Our current mix + 1 = final distance
|
||||
distance_to_final_mix = dest_mix_distance + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're out of range for our distance, mark it as no final mix
|
||||
if (distance_to_final_mix >= info_count) {
|
||||
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
|
||||
}
|
||||
|
||||
in_params.final_mix_distance = distance_to_final_mix;
|
||||
}
|
||||
}
|
||||
|
||||
void MixContext::CalcMixBufferOffset() {
|
||||
s32 offset{};
|
||||
for (std::size_t i = 0; i < info_count; i++) {
|
||||
auto& info = GetSortedInfo(i);
|
||||
auto& in_params = info.GetInParams();
|
||||
if (in_params.in_use) {
|
||||
// Only update if in use
|
||||
in_params.buffer_offset = offset;
|
||||
offset += in_params.buffer_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MixContext::SortInfo() {
|
||||
// Get the distance to the final mix
|
||||
UpdateDistancesFromFinalMix();
|
||||
|
||||
// Sort based on the distance to the final mix
|
||||
std::sort(sorted_info.begin(), sorted_info.end(),
|
||||
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
|
||||
return lhs->GetInParams().final_mix_distance >
|
||||
rhs->GetInParams().final_mix_distance;
|
||||
});
|
||||
|
||||
// Calculate the mix buffer offset
|
||||
CalcMixBufferOffset();
|
||||
}
|
||||
|
||||
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
|
||||
// If we're not using mixes, just calculate the mix buffer offset
|
||||
if (!splitter_context.UsingSplitter()) {
|
||||
CalcMixBufferOffset();
|
||||
return true;
|
||||
}
|
||||
// Sort our node states
|
||||
if (!node_states.Tsort(edge_matrix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get our sorted list
|
||||
const auto sorted_list = node_states.GetIndexList();
|
||||
std::size_t info_id{};
|
||||
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
|
||||
// Set our sorted info
|
||||
sorted_info[info_id++] = &GetInfo(*itr);
|
||||
}
|
||||
|
||||
// Calculate the mix buffer offset
|
||||
CalcMixBufferOffset();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t MixContext::GetCount() const {
|
||||
return info_count;
|
||||
}
|
||||
|
||||
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
|
||||
ASSERT(i < info_count);
|
||||
return infos.at(i);
|
||||
}
|
||||
|
||||
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
|
||||
ASSERT(i < info_count);
|
||||
return infos.at(i);
|
||||
}
|
||||
|
||||
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
|
||||
ASSERT(i < info_count);
|
||||
return *sorted_info.at(i);
|
||||
}
|
||||
|
||||
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
|
||||
ASSERT(i < info_count);
|
||||
return *sorted_info.at(i);
|
||||
}
|
||||
|
||||
ServerMixInfo& MixContext::GetFinalMixInfo() {
|
||||
return infos.at(AudioCommon::FINAL_MIX);
|
||||
}
|
||||
|
||||
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
|
||||
return infos.at(AudioCommon::FINAL_MIX);
|
||||
}
|
||||
|
||||
EdgeMatrix& MixContext::GetEdgeMatrix() {
|
||||
return edge_matrix;
|
||||
}
|
||||
|
||||
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
|
||||
return edge_matrix;
|
||||
}
|
||||
|
||||
ServerMixInfo::ServerMixInfo() {
|
||||
Cleanup();
|
||||
}
|
||||
ServerMixInfo::~ServerMixInfo() = default;
|
||||
|
||||
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
|
||||
return in_params;
|
||||
}
|
||||
|
||||
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
|
||||
return in_params;
|
||||
}
|
||||
|
||||
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
||||
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
|
||||
EffectContext& effect_context) {
|
||||
in_params.volume = mix_in.volume;
|
||||
in_params.sample_rate = mix_in.sample_rate;
|
||||
in_params.buffer_count = mix_in.buffer_count;
|
||||
in_params.in_use = mix_in.in_use;
|
||||
in_params.mix_id = mix_in.mix_id;
|
||||
in_params.node_id = mix_in.node_id;
|
||||
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
|
||||
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
|
||||
in_params.mix_volume[i].begin());
|
||||
}
|
||||
|
||||
bool require_sort = false;
|
||||
|
||||
if (behavior_info.IsSplitterSupported()) {
|
||||
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
|
||||
} else {
|
||||
in_params.dest_mix_id = mix_in.dest_mix_id;
|
||||
in_params.splitter_id = AudioCommon::NO_SPLITTER;
|
||||
}
|
||||
|
||||
ResetEffectProcessingOrder();
|
||||
const auto effect_count = effect_context.GetCount();
|
||||
for (std::size_t i = 0; i < effect_count; i++) {
|
||||
auto* effect_info = effect_context.GetInfo(i);
|
||||
if (effect_info->GetMixID() == in_params.mix_id) {
|
||||
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(ogniK): Update effect processing order
|
||||
return require_sort;
|
||||
}
|
||||
|
||||
bool ServerMixInfo::HasAnyConnection() const {
|
||||
return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
|
||||
in_params.mix_id != AudioCommon::NO_MIX;
|
||||
}
|
||||
|
||||
void ServerMixInfo::Cleanup() {
|
||||
in_params.volume = 0.0f;
|
||||
in_params.sample_rate = 0;
|
||||
in_params.buffer_count = 0;
|
||||
in_params.in_use = false;
|
||||
in_params.mix_id = AudioCommon::NO_MIX;
|
||||
in_params.node_id = 0;
|
||||
in_params.buffer_offset = 0;
|
||||
in_params.dest_mix_id = AudioCommon::NO_MIX;
|
||||
in_params.splitter_id = AudioCommon::NO_SPLITTER;
|
||||
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
|
||||
}
|
||||
|
||||
void ServerMixInfo::SetEffectCount(std::size_t count) {
|
||||
effect_processing_order.resize(count);
|
||||
ResetEffectProcessingOrder();
|
||||
}
|
||||
|
||||
void ServerMixInfo::ResetEffectProcessingOrder() {
|
||||
for (auto& order : effect_processing_order) {
|
||||
order = AudioCommon::NO_EFFECT_ORDER;
|
||||
}
|
||||
}
|
||||
|
||||
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
|
||||
return effect_processing_order.at(i);
|
||||
}
|
||||
|
||||
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
||||
SplitterContext& splitter_context) {
|
||||
// Mixes are identical
|
||||
if (in_params.dest_mix_id == mix_in.dest_mix_id &&
|
||||
in_params.splitter_id == mix_in.splitter_id &&
|
||||
((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
|
||||
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
|
||||
return false;
|
||||
}
|
||||
// Remove current edges for mix id
|
||||
edge_matrix.RemoveEdges(in_params.mix_id);
|
||||
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
|
||||
// If we have a valid destination mix id, set our edge matrix
|
||||
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
|
||||
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
|
||||
// Recurse our splitter linked and set our edges
|
||||
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
|
||||
const auto length = splitter_info.GetLength();
|
||||
for (s32 i = 0; i < length; i++) {
|
||||
const auto* splitter_destination =
|
||||
splitter_context.GetDestinationData(mix_in.splitter_id, i);
|
||||
if (splitter_destination == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (splitter_destination->ValidMixId()) {
|
||||
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
|
||||
}
|
||||
}
|
||||
}
|
||||
in_params.dest_mix_id = mix_in.dest_mix_id;
|
||||
in_params.splitter_id = mix_in.splitter_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
class BehaviorInfo;
|
||||
class EffectContext;
|
||||
|
||||
class MixInfo {
|
||||
public:
|
||||
struct DirtyHeader {
|
||||
u32_le magic{};
|
||||
u32_le mixer_count{};
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
};
|
||||
static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
|
||||
|
||||
struct InParams {
|
||||
float_le volume{};
|
||||
s32_le sample_rate{};
|
||||
s32_le buffer_count{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
s32_le mix_id{};
|
||||
s32_le effect_count{};
|
||||
u32_le node_id{};
|
||||
INSERT_PADDING_WORDS(2);
|
||||
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
|
||||
mix_volume{};
|
||||
s32_le dest_mix_id{};
|
||||
s32_le splitter_id{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
|
||||
};
|
||||
|
||||
class ServerMixInfo {
|
||||
public:
|
||||
struct InParams {
|
||||
float volume{};
|
||||
s32 sample_rate{};
|
||||
s32 buffer_count{};
|
||||
bool in_use{};
|
||||
s32 mix_id{};
|
||||
u32 node_id{};
|
||||
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
|
||||
mix_volume{};
|
||||
s32 dest_mix_id{};
|
||||
s32 splitter_id{};
|
||||
s32 buffer_offset{};
|
||||
s32 final_mix_distance{};
|
||||
};
|
||||
ServerMixInfo();
|
||||
~ServerMixInfo();
|
||||
|
||||
const ServerMixInfo::InParams& GetInParams() const;
|
||||
ServerMixInfo::InParams& GetInParams();
|
||||
|
||||
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
||||
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
|
||||
EffectContext& effect_context);
|
||||
bool HasAnyConnection() const;
|
||||
void Cleanup();
|
||||
void SetEffectCount(std::size_t count);
|
||||
void ResetEffectProcessingOrder();
|
||||
s32 GetEffectOrder(std::size_t i) const;
|
||||
|
||||
private:
|
||||
std::vector<s32> effect_processing_order;
|
||||
InParams in_params{};
|
||||
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
||||
SplitterContext& splitter_context);
|
||||
};
|
||||
|
||||
class MixContext {
|
||||
public:
|
||||
MixContext();
|
||||
~MixContext();
|
||||
|
||||
void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
|
||||
std::size_t effect_count);
|
||||
void SortInfo();
|
||||
bool TsortInfo(SplitterContext& splitter_context);
|
||||
|
||||
std::size_t GetCount() const;
|
||||
ServerMixInfo& GetInfo(std::size_t i);
|
||||
const ServerMixInfo& GetInfo(std::size_t i) const;
|
||||
ServerMixInfo& GetSortedInfo(std::size_t i);
|
||||
const ServerMixInfo& GetSortedInfo(std::size_t i) const;
|
||||
ServerMixInfo& GetFinalMixInfo();
|
||||
const ServerMixInfo& GetFinalMixInfo() const;
|
||||
EdgeMatrix& GetEdgeMatrix();
|
||||
const EdgeMatrix& GetEdgeMatrix() const;
|
||||
|
||||
private:
|
||||
void CalcMixBufferOffset();
|
||||
void UpdateDistancesFromFinalMix();
|
||||
|
||||
NodeStates node_states{};
|
||||
EdgeMatrix edge_matrix{};
|
||||
std::size_t info_count{};
|
||||
std::vector<ServerMixInfo> infos{};
|
||||
std::vector<ServerMixInfo*> sorted_info{};
|
||||
};
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/sink_context.h"
|
||||
|
||||
namespace AudioCore {
|
||||
SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
|
||||
SinkContext::~SinkContext() = default;
|
||||
|
||||
std::size_t SinkContext::GetCount() const {
|
||||
return sink_count;
|
||||
}
|
||||
|
||||
void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
|
||||
in_use = in.in_use;
|
||||
use_count = in.device.input_count;
|
||||
std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
|
||||
}
|
||||
|
||||
bool SinkContext::InUse() const {
|
||||
return in_use;
|
||||
}
|
||||
|
||||
std::vector<u8> SinkContext::OutputBuffers() const {
|
||||
std::vector<u8> buffer_ret(use_count);
|
||||
std::memcpy(buffer_ret.data(), buffers.data(), use_count);
|
||||
return buffer_ret;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/common.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
enum class SinkTypes : u8 {
|
||||
Invalid = 0,
|
||||
Device = 1,
|
||||
Circular = 2,
|
||||
};
|
||||
|
||||
enum class SinkSampleFormat : u32_le {
|
||||
None = 0,
|
||||
Pcm8 = 1,
|
||||
Pcm16 = 2,
|
||||
Pcm24 = 3,
|
||||
Pcm32 = 4,
|
||||
PcmFloat = 5,
|
||||
Adpcm = 6,
|
||||
};
|
||||
|
||||
class SinkInfo {
|
||||
public:
|
||||
struct CircularBufferIn {
|
||||
u64_le address;
|
||||
u32_le size;
|
||||
u32_le input_count;
|
||||
u32_le sample_count;
|
||||
u32_le previous_position;
|
||||
SinkSampleFormat sample_format;
|
||||
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
|
||||
bool in_use;
|
||||
INSERT_UNION_PADDING_BYTES(5);
|
||||
};
|
||||
static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
|
||||
"SinkInfo::CircularBufferIn is in invalid size");
|
||||
|
||||
struct DeviceIn {
|
||||
std::array<u8, 255> device_name;
|
||||
INSERT_UNION_PADDING_BYTES(1);
|
||||
s32_le input_count;
|
||||
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
|
||||
INSERT_UNION_PADDING_BYTES(1);
|
||||
bool down_matrix_enabled;
|
||||
std::array<float_le, 4> down_matrix_coef;
|
||||
};
|
||||
static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
|
||||
|
||||
struct InParams {
|
||||
SinkTypes type{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(2);
|
||||
u32_le node_id{};
|
||||
INSERT_PADDING_WORDS(6);
|
||||
union {
|
||||
// std::array<u8, 0x120> raw{};
|
||||
SinkInfo::DeviceIn device;
|
||||
SinkInfo::CircularBufferIn circular_buffer;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
|
||||
};
|
||||
|
||||
class SinkContext {
|
||||
public:
|
||||
explicit SinkContext(std::size_t sink_count);
|
||||
~SinkContext();
|
||||
|
||||
std::size_t GetCount() const;
|
||||
|
||||
void UpdateMainSink(SinkInfo::InParams& in);
|
||||
bool InUse() const;
|
||||
std::vector<u8> OutputBuffers() const;
|
||||
|
||||
private:
|
||||
bool in_use{false};
|
||||
s32 use_count{};
|
||||
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
|
||||
std::size_t sink_count{};
|
||||
};
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,617 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/splitter_context.h"
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
|
||||
ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
|
||||
|
||||
void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
|
||||
// Log error as these are not actually failure states
|
||||
if (header.magic != SplitterMagic::DataHeader) {
|
||||
LOG_ERROR(Audio, "Splitter destination header is invalid!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Incorrect splitter id
|
||||
if (header.splitter_id != id) {
|
||||
LOG_ERROR(Audio, "Splitter destination ids do not match!");
|
||||
return;
|
||||
}
|
||||
|
||||
mix_id = header.mix_id;
|
||||
// Copy our mix volumes
|
||||
std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
|
||||
if (!in_use && header.in_use) {
|
||||
// Update mix volumes
|
||||
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
|
||||
needs_update = false;
|
||||
}
|
||||
in_use = header.in_use;
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
|
||||
return next;
|
||||
}
|
||||
|
||||
const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
|
||||
return next;
|
||||
}
|
||||
|
||||
void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
|
||||
next = dest;
|
||||
}
|
||||
|
||||
bool ServerSplitterDestinationData::ValidMixId() const {
|
||||
return GetMixId() != AudioCommon::NO_MIX;
|
||||
}
|
||||
|
||||
s32 ServerSplitterDestinationData::GetMixId() const {
|
||||
return mix_id;
|
||||
}
|
||||
|
||||
bool ServerSplitterDestinationData::IsConfigured() const {
|
||||
return in_use && ValidMixId();
|
||||
}
|
||||
|
||||
float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
|
||||
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
|
||||
return current_mix_volumes.at(i);
|
||||
}
|
||||
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
||||
ServerSplitterDestinationData::CurrentMixVolumes() const {
|
||||
return current_mix_volumes;
|
||||
}
|
||||
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
||||
ServerSplitterDestinationData::LastMixVolumes() const {
|
||||
return last_mix_volumes;
|
||||
}
|
||||
|
||||
void ServerSplitterDestinationData::MarkDirty() {
|
||||
needs_update = true;
|
||||
}
|
||||
|
||||
void ServerSplitterDestinationData::UpdateInternalState() {
|
||||
if (in_use && needs_update) {
|
||||
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
|
||||
}
|
||||
needs_update = false;
|
||||
}
|
||||
|
||||
ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
|
||||
ServerSplitterInfo::~ServerSplitterInfo() = default;
|
||||
|
||||
void ServerSplitterInfo::InitializeInfos() {
|
||||
send_length = 0;
|
||||
head = nullptr;
|
||||
new_connection = true;
|
||||
}
|
||||
|
||||
void ServerSplitterInfo::ClearNewConnectionFlag() {
|
||||
new_connection = false;
|
||||
}
|
||||
|
||||
std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
|
||||
if (header.send_id != id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sample_rate = header.sample_rate;
|
||||
new_connection = true;
|
||||
// We need to update the size here due to the splitter bug being present and providing an
|
||||
// incorrect size. We're suppose to also update the header here but we just ignore and continue
|
||||
return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
|
||||
return head;
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
|
||||
auto current_head = head;
|
||||
for (std::size_t i = 0; i < depth; i++) {
|
||||
if (current_head == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
current_head = current_head->GetNextDestination();
|
||||
}
|
||||
return current_head;
|
||||
}
|
||||
|
||||
const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
|
||||
auto current_head = head;
|
||||
for (std::size_t i = 0; i < depth; i++) {
|
||||
if (current_head == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
current_head = current_head->GetNextDestination();
|
||||
}
|
||||
return current_head;
|
||||
}
|
||||
|
||||
bool ServerSplitterInfo::HasNewConnection() const {
|
||||
return new_connection;
|
||||
}
|
||||
|
||||
s32 ServerSplitterInfo::GetLength() const {
|
||||
return send_length;
|
||||
}
|
||||
|
||||
void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
|
||||
head = new_head;
|
||||
}
|
||||
|
||||
void ServerSplitterInfo::SetHeadDepth(s32 length) {
|
||||
send_length = length;
|
||||
}
|
||||
|
||||
SplitterContext::SplitterContext() = default;
|
||||
SplitterContext::~SplitterContext() = default;
|
||||
|
||||
void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
|
||||
std::size_t _data_count) {
|
||||
if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
|
||||
Setup(0, 0, false);
|
||||
return;
|
||||
}
|
||||
// Only initialize if we're using splitters
|
||||
Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
|
||||
}
|
||||
|
||||
bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
|
||||
std::size_t& bytes_read) {
|
||||
const auto UpdateOffsets = [&](std::size_t read) {
|
||||
input_offset += read;
|
||||
bytes_read += read;
|
||||
};
|
||||
|
||||
if (info_count == 0 || data_count == 0) {
|
||||
bytes_read = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
||||
sizeof(SplitterInfo::InHeader))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
SplitterInfo::InHeader header{};
|
||||
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
|
||||
UpdateOffsets(sizeof(SplitterInfo::InHeader));
|
||||
|
||||
if (header.magic != SplitterMagic::SplitterHeader) {
|
||||
LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
|
||||
SplitterMagic::SplitterHeader, header.magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear all connections
|
||||
for (auto& info : infos) {
|
||||
info.ClearNewConnectionFlag();
|
||||
}
|
||||
|
||||
UpdateInfo(input, input_offset, bytes_read, header.info_count);
|
||||
UpdateData(input, input_offset, bytes_read, header.data_count);
|
||||
const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
|
||||
input_offset += aligned_bytes_read - bytes_read;
|
||||
bytes_read = aligned_bytes_read;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitterContext::UsingSplitter() const {
|
||||
return info_count > 0 && data_count > 0;
|
||||
}
|
||||
|
||||
ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
|
||||
ASSERT(i < info_count);
|
||||
return infos.at(i);
|
||||
}
|
||||
|
||||
const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
|
||||
ASSERT(i < info_count);
|
||||
return infos.at(i);
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
|
||||
ASSERT(i < data_count);
|
||||
return datas.at(i);
|
||||
}
|
||||
|
||||
const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
|
||||
ASSERT(i < data_count);
|
||||
return datas.at(i);
|
||||
}
|
||||
|
||||
ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
|
||||
std::size_t data) {
|
||||
ASSERT(info < info_count);
|
||||
auto& cur_info = GetInfo(info);
|
||||
return cur_info.GetData(data);
|
||||
}
|
||||
|
||||
const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
|
||||
std::size_t data) const {
|
||||
ASSERT(info < info_count);
|
||||
auto& cur_info = GetInfo(info);
|
||||
return cur_info.GetData(data);
|
||||
}
|
||||
|
||||
void SplitterContext::UpdateInternalState() {
|
||||
if (data_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& data : datas) {
|
||||
data.UpdateInternalState();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t SplitterContext::GetInfoCount() const {
|
||||
return info_count;
|
||||
}
|
||||
|
||||
std::size_t SplitterContext::GetDataCount() const {
|
||||
return data_count;
|
||||
}
|
||||
|
||||
void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
|
||||
bool is_splitter_bug_fixed) {
|
||||
|
||||
info_count = _info_count;
|
||||
data_count = _data_count;
|
||||
|
||||
for (std::size_t i = 0; i < info_count; i++) {
|
||||
auto& splitter = infos.emplace_back(static_cast<s32>(i));
|
||||
splitter.InitializeInfos();
|
||||
}
|
||||
for (std::size_t i = 0; i < data_count; i++) {
|
||||
datas.emplace_back(static_cast<s32>(i));
|
||||
}
|
||||
|
||||
bug_fixed = is_splitter_bug_fixed;
|
||||
}
|
||||
|
||||
bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
|
||||
std::size_t& bytes_read, s32 in_splitter_count) {
|
||||
const auto UpdateOffsets = [&](std::size_t read) {
|
||||
input_offset += read;
|
||||
bytes_read += read;
|
||||
};
|
||||
|
||||
for (s32 i = 0; i < in_splitter_count; i++) {
|
||||
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
||||
sizeof(SplitterInfo::InInfoPrams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
SplitterInfo::InInfoPrams header{};
|
||||
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
|
||||
|
||||
// Logged as warning as these don't actually cause a bailout for some reason
|
||||
if (header.magic != SplitterMagic::InfoHeader) {
|
||||
LOG_ERROR(Audio, "Bad splitter data header");
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.send_id < 0 || header.send_id > info_count) {
|
||||
LOG_ERROR(Audio, "Bad splitter data id");
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
|
||||
auto& info = GetInfo(header.send_id);
|
||||
if (!RecomposeDestination(info, header, input, input_offset)) {
|
||||
LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
|
||||
return false;
|
||||
}
|
||||
const std::size_t read = info.Update(header);
|
||||
bytes_read += read;
|
||||
input_offset += read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
|
||||
std::size_t& bytes_read, s32 in_data_count) {
|
||||
const auto UpdateOffsets = [&](std::size_t read) {
|
||||
input_offset += read;
|
||||
bytes_read += read;
|
||||
};
|
||||
|
||||
for (s32 i = 0; i < in_data_count; i++) {
|
||||
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
||||
sizeof(SplitterInfo::InDestinationParams))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
SplitterInfo::InDestinationParams header{};
|
||||
std::memcpy(&header, input.data() + input_offset,
|
||||
sizeof(SplitterInfo::InDestinationParams));
|
||||
UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
|
||||
|
||||
// Logged as warning as these don't actually cause a bailout for some reason
|
||||
if (header.magic != SplitterMagic::DataHeader) {
|
||||
LOG_ERROR(Audio, "Bad splitter data header");
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.splitter_id < 0 || header.splitter_id > data_count) {
|
||||
LOG_ERROR(Audio, "Bad splitter data id");
|
||||
break;
|
||||
}
|
||||
GetData(header.splitter_id).Update(header);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
|
||||
SplitterInfo::InInfoPrams& header,
|
||||
const std::vector<u8>& input,
|
||||
const std::size_t& input_offset) {
|
||||
// Clear our current destinations
|
||||
auto* current_head = info.GetHead();
|
||||
while (current_head != nullptr) {
|
||||
auto next_head = current_head->GetNextDestination();
|
||||
current_head->SetNextDestination(nullptr);
|
||||
current_head = next_head;
|
||||
}
|
||||
info.SetHead(nullptr);
|
||||
|
||||
s32 size = header.length;
|
||||
// If the splitter bug is present, calculate fixed size
|
||||
if (!bug_fixed) {
|
||||
if (info_count > 0) {
|
||||
const auto factor = data_count / info_count;
|
||||
size = std::min(header.length, static_cast<s32>(factor));
|
||||
} else {
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (size < 1) {
|
||||
LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto* start_head = &GetData(header.resource_id_base);
|
||||
current_head = start_head;
|
||||
std::vector<s32_le> resource_ids(size - 1);
|
||||
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
|
||||
resource_ids.size() * sizeof(s32_le))) {
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!");
|
||||
return false;
|
||||
}
|
||||
std::memcpy(resource_ids.data(), input.data() + input_offset,
|
||||
resource_ids.size() * sizeof(s32_le));
|
||||
|
||||
for (auto resource_id : resource_ids) {
|
||||
auto* head = &GetData(resource_id);
|
||||
current_head->SetNextDestination(head);
|
||||
current_head = head;
|
||||
}
|
||||
|
||||
info.SetHead(start_head);
|
||||
info.SetHeadDepth(size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NodeStates::NodeStates() = default;
|
||||
NodeStates::~NodeStates() = default;
|
||||
|
||||
void NodeStates::Initialize(std::size_t node_count_) {
|
||||
// Setup our work parameters
|
||||
node_count = node_count_;
|
||||
was_node_found.resize(node_count);
|
||||
was_node_completed.resize(node_count);
|
||||
index_list.resize(node_count);
|
||||
index_stack.Reset(node_count * node_count);
|
||||
}
|
||||
|
||||
bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
|
||||
return DepthFirstSearch(edge_matrix);
|
||||
}
|
||||
|
||||
std::size_t NodeStates::GetIndexPos() const {
|
||||
return index_pos;
|
||||
}
|
||||
|
||||
const std::vector<s32>& NodeStates::GetIndexList() const {
|
||||
return index_list;
|
||||
}
|
||||
|
||||
void NodeStates::PushTsortResult(s32 index) {
|
||||
ASSERT(index < node_count);
|
||||
index_list[index_pos++] = index;
|
||||
}
|
||||
|
||||
bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
|
||||
ResetState();
|
||||
for (std::size_t i = 0; i < node_count; i++) {
|
||||
const auto node_id = static_cast<s32>(i);
|
||||
|
||||
// If we don't have a state, send to our index stack for work
|
||||
if (GetState(i) == NodeStates::State::NoState) {
|
||||
index_stack.push(node_id);
|
||||
}
|
||||
|
||||
// While we have work to do in our stack
|
||||
while (index_stack.Count() > 0) {
|
||||
// Get the current node
|
||||
const auto current_stack_index = index_stack.top();
|
||||
// Check if we've seen the node yet
|
||||
const auto index_state = GetState(current_stack_index);
|
||||
if (index_state == NodeStates::State::NoState) {
|
||||
// Mark the node as seen
|
||||
UpdateState(NodeStates::State::InFound, current_stack_index);
|
||||
} else if (index_state == NodeStates::State::InFound) {
|
||||
// We've seen this node before, mark it as completed
|
||||
UpdateState(NodeStates::State::InCompleted, current_stack_index);
|
||||
// Update our index list
|
||||
PushTsortResult(current_stack_index);
|
||||
// Pop the stack
|
||||
index_stack.pop();
|
||||
continue;
|
||||
} else if (index_state == NodeStates::State::InCompleted) {
|
||||
// If our node is already sorted, clear it
|
||||
index_stack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto node_count = edge_matrix.GetNodeCount();
|
||||
for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
|
||||
// Check if our node is connected to our edge matrix
|
||||
if (!edge_matrix.Connected(current_stack_index, j)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if our node exists
|
||||
const auto node_state = GetState(j);
|
||||
if (node_state == NodeStates::State::NoState) {
|
||||
// Add more work
|
||||
index_stack.push(j);
|
||||
} else if (node_state == NodeStates::State::InFound) {
|
||||
UNREACHABLE_MSG("Node start marked as found");
|
||||
ResetState();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NodeStates::ResetState() {
|
||||
// Reset to the start of our index stack
|
||||
index_pos = 0;
|
||||
for (std::size_t i = 0; i < node_count; i++) {
|
||||
// Mark all nodes as not found
|
||||
was_node_found[i] = false;
|
||||
// Mark all nodes as uncompleted
|
||||
was_node_completed[i] = false;
|
||||
// Mark all indexes as invalid
|
||||
index_list[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
|
||||
switch (state) {
|
||||
case NodeStates::State::NoState:
|
||||
was_node_found[i] = false;
|
||||
was_node_completed[i] = false;
|
||||
break;
|
||||
case NodeStates::State::InFound:
|
||||
was_node_found[i] = true;
|
||||
was_node_completed[i] = false;
|
||||
break;
|
||||
case NodeStates::State::InCompleted:
|
||||
was_node_found[i] = false;
|
||||
was_node_completed[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NodeStates::State NodeStates::GetState(std::size_t i) {
|
||||
ASSERT(i < node_count);
|
||||
if (was_node_found[i]) {
|
||||
// If our node exists in our found list
|
||||
return NodeStates::State::InFound;
|
||||
} else if (was_node_completed[i]) {
|
||||
// If node is in the completed list
|
||||
return NodeStates::State::InCompleted;
|
||||
} else {
|
||||
// If in neither
|
||||
return NodeStates::State::NoState;
|
||||
}
|
||||
}
|
||||
|
||||
NodeStates::Stack::Stack() = default;
|
||||
NodeStates::Stack::~Stack() = default;
|
||||
|
||||
void NodeStates::Stack::Reset(std::size_t size) {
|
||||
// Mark our stack as empty
|
||||
stack.resize(size);
|
||||
stack_size = size;
|
||||
stack_pos = 0;
|
||||
std::fill(stack.begin(), stack.end(), 0);
|
||||
}
|
||||
|
||||
void NodeStates::Stack::push(s32 val) {
|
||||
ASSERT(stack_pos < stack_size);
|
||||
stack[stack_pos++] = val;
|
||||
}
|
||||
|
||||
std::size_t NodeStates::Stack::Count() const {
|
||||
return stack_pos;
|
||||
}
|
||||
|
||||
s32 NodeStates::Stack::top() const {
|
||||
ASSERT(stack_pos > 0);
|
||||
return stack[stack_pos - 1];
|
||||
}
|
||||
|
||||
s32 NodeStates::Stack::pop() {
|
||||
ASSERT(stack_pos > 0);
|
||||
stack_pos--;
|
||||
return stack[stack_pos];
|
||||
}
|
||||
|
||||
EdgeMatrix::EdgeMatrix() = default;
|
||||
EdgeMatrix::~EdgeMatrix() = default;
|
||||
|
||||
void EdgeMatrix::Initialize(std::size_t _node_count) {
|
||||
node_count = _node_count;
|
||||
edge_matrix.resize(node_count * node_count);
|
||||
}
|
||||
|
||||
bool EdgeMatrix::Connected(s32 a, s32 b) {
|
||||
return GetState(a, b);
|
||||
}
|
||||
|
||||
void EdgeMatrix::Connect(s32 a, s32 b) {
|
||||
SetState(a, b, true);
|
||||
}
|
||||
|
||||
void EdgeMatrix::Disconnect(s32 a, s32 b) {
|
||||
SetState(a, b, false);
|
||||
}
|
||||
|
||||
void EdgeMatrix::RemoveEdges(s32 edge) {
|
||||
for (std::size_t i = 0; i < node_count; i++) {
|
||||
SetState(edge, static_cast<s32>(i), false);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t EdgeMatrix::GetNodeCount() const {
|
||||
return node_count;
|
||||
}
|
||||
|
||||
void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
|
||||
ASSERT(InRange(a, b));
|
||||
edge_matrix.at(a * node_count + b) = state;
|
||||
}
|
||||
|
||||
bool EdgeMatrix::GetState(s32 a, s32 b) {
|
||||
ASSERT(InRange(a, b));
|
||||
return edge_matrix.at(a * node_count + b);
|
||||
}
|
||||
|
||||
bool EdgeMatrix::InRange(s32 a, s32 b) const {
|
||||
const std::size_t pos = a * node_count + b;
|
||||
return pos < (node_count * node_count);
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,221 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include "audio_core/common.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace AudioCore {
|
||||
class BehaviorInfo;
|
||||
|
||||
class EdgeMatrix {
|
||||
public:
|
||||
EdgeMatrix();
|
||||
~EdgeMatrix();
|
||||
|
||||
void Initialize(std::size_t _node_count);
|
||||
bool Connected(s32 a, s32 b);
|
||||
void Connect(s32 a, s32 b);
|
||||
void Disconnect(s32 a, s32 b);
|
||||
void RemoveEdges(s32 edge);
|
||||
std::size_t GetNodeCount() const;
|
||||
|
||||
private:
|
||||
void SetState(s32 a, s32 b, bool state);
|
||||
bool GetState(s32 a, s32 b);
|
||||
|
||||
bool InRange(s32 a, s32 b) const;
|
||||
std::vector<bool> edge_matrix{};
|
||||
std::size_t node_count{};
|
||||
};
|
||||
|
||||
class NodeStates {
|
||||
public:
|
||||
enum class State {
|
||||
NoState = 0,
|
||||
InFound = 1,
|
||||
InCompleted = 2,
|
||||
};
|
||||
|
||||
// Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
|
||||
class Stack {
|
||||
public:
|
||||
Stack();
|
||||
~Stack();
|
||||
|
||||
void Reset(std::size_t size);
|
||||
void push(s32 val);
|
||||
std::size_t Count() const;
|
||||
s32 top() const;
|
||||
s32 pop();
|
||||
|
||||
private:
|
||||
std::vector<s32> stack{};
|
||||
std::size_t stack_size{};
|
||||
std::size_t stack_pos{};
|
||||
};
|
||||
NodeStates();
|
||||
~NodeStates();
|
||||
|
||||
void Initialize(std::size_t _node_count);
|
||||
bool Tsort(EdgeMatrix& edge_matrix);
|
||||
std::size_t GetIndexPos() const;
|
||||
const std::vector<s32>& GetIndexList() const;
|
||||
|
||||
private:
|
||||
void PushTsortResult(s32 index);
|
||||
bool DepthFirstSearch(EdgeMatrix& edge_matrix);
|
||||
void ResetState();
|
||||
void UpdateState(NodeStates::State state, std::size_t i);
|
||||
NodeStates::State GetState(std::size_t i);
|
||||
|
||||
std::size_t node_count{};
|
||||
std::vector<bool> was_node_found{};
|
||||
std::vector<bool> was_node_completed{};
|
||||
std::size_t index_pos{};
|
||||
std::vector<s32> index_list{};
|
||||
NodeStates::Stack index_stack{};
|
||||
};
|
||||
|
||||
enum class SplitterMagic : u32_le {
|
||||
SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
|
||||
DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
|
||||
InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
|
||||
};
|
||||
|
||||
class SplitterInfo {
|
||||
public:
|
||||
struct InHeader {
|
||||
SplitterMagic magic{};
|
||||
s32_le info_count{};
|
||||
s32_le data_count{};
|
||||
INSERT_PADDING_WORDS(5);
|
||||
};
|
||||
static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
|
||||
"SplitterInfo::InHeader is an invalid size");
|
||||
|
||||
struct InInfoPrams {
|
||||
SplitterMagic magic{};
|
||||
s32_le send_id{};
|
||||
s32_le sample_rate{};
|
||||
s32_le length{};
|
||||
s32_le resource_id_base{};
|
||||
};
|
||||
static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
|
||||
"SplitterInfo::InInfoPrams is an invalid size");
|
||||
|
||||
struct InDestinationParams {
|
||||
SplitterMagic magic{};
|
||||
s32_le splitter_id{};
|
||||
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
|
||||
s32_le mix_id{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
|
||||
"SplitterInfo::InDestinationParams is an invalid size");
|
||||
};
|
||||
|
||||
class ServerSplitterDestinationData {
|
||||
public:
|
||||
explicit ServerSplitterDestinationData(s32 id);
|
||||
~ServerSplitterDestinationData();
|
||||
|
||||
void Update(SplitterInfo::InDestinationParams& header);
|
||||
|
||||
ServerSplitterDestinationData* GetNextDestination();
|
||||
const ServerSplitterDestinationData* GetNextDestination() const;
|
||||
void SetNextDestination(ServerSplitterDestinationData* dest);
|
||||
bool ValidMixId() const;
|
||||
s32 GetMixId() const;
|
||||
bool IsConfigured() const;
|
||||
float GetMixVolume(std::size_t i) const;
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
|
||||
void MarkDirty();
|
||||
void UpdateInternalState();
|
||||
|
||||
private:
|
||||
bool needs_update{};
|
||||
bool in_use{};
|
||||
s32 id{};
|
||||
s32 mix_id{};
|
||||
std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
|
||||
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
|
||||
ServerSplitterDestinationData* next = nullptr;
|
||||
};
|
||||
|
||||
class ServerSplitterInfo {
|
||||
public:
|
||||
explicit ServerSplitterInfo(s32 id);
|
||||
~ServerSplitterInfo();
|
||||
|
||||
void InitializeInfos();
|
||||
void ClearNewConnectionFlag();
|
||||
std::size_t Update(SplitterInfo::InInfoPrams& header);
|
||||
|
||||
ServerSplitterDestinationData* GetHead();
|
||||
const ServerSplitterDestinationData* GetHead() const;
|
||||
ServerSplitterDestinationData* GetData(std::size_t depth);
|
||||
const ServerSplitterDestinationData* GetData(std::size_t depth) const;
|
||||
|
||||
bool HasNewConnection() const;
|
||||
s32 GetLength() const;
|
||||
|
||||
void SetHead(ServerSplitterDestinationData* new_head);
|
||||
void SetHeadDepth(s32 length);
|
||||
|
||||
private:
|
||||
s32 sample_rate{};
|
||||
s32 id{};
|
||||
s32 send_length{};
|
||||
ServerSplitterDestinationData* head = nullptr;
|
||||
bool new_connection{};
|
||||
};
|
||||
|
||||
class SplitterContext {
|
||||
public:
|
||||
SplitterContext();
|
||||
~SplitterContext();
|
||||
|
||||
void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
|
||||
std::size_t data_count);
|
||||
|
||||
bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
|
||||
bool UsingSplitter() const;
|
||||
|
||||
ServerSplitterInfo& GetInfo(std::size_t i);
|
||||
const ServerSplitterInfo& GetInfo(std::size_t i) const;
|
||||
ServerSplitterDestinationData& GetData(std::size_t i);
|
||||
const ServerSplitterDestinationData& GetData(std::size_t i) const;
|
||||
ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
|
||||
const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
|
||||
std::size_t data) const;
|
||||
void UpdateInternalState();
|
||||
|
||||
std::size_t GetInfoCount() const;
|
||||
std::size_t GetDataCount() const;
|
||||
|
||||
private:
|
||||
void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
|
||||
bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
|
||||
std::size_t& bytes_read, s32 in_splitter_count);
|
||||
bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
|
||||
std::size_t& bytes_read, s32 in_data_count);
|
||||
bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
|
||||
const std::vector<u8>& input, const std::size_t& input_offset);
|
||||
|
||||
std::vector<ServerSplitterInfo> infos{};
|
||||
std::vector<ServerSplitterDestinationData> datas{};
|
||||
|
||||
std::size_t info_count{};
|
||||
std::size_t data_count{};
|
||||
bool bug_fixed{};
|
||||
};
|
||||
} // namespace AudioCore
|
||||
@@ -104,11 +104,7 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue()
|
||||
? std::chrono::nanoseconds::zero()
|
||||
: ns_late;
|
||||
const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
|
||||
core_timing.ScheduleEvent(future_time, release_event, {});
|
||||
core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/voice_context.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
|
||||
ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
|
||||
|
||||
bool ServerVoiceChannelResource::InUse() const {
|
||||
return in_use;
|
||||
}
|
||||
|
||||
float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
|
||||
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
|
||||
return mix_volume.at(i);
|
||||
}
|
||||
|
||||
float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
|
||||
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
|
||||
return last_mix_volume.at(i);
|
||||
}
|
||||
|
||||
void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
|
||||
in_use = in_params.in_use;
|
||||
// Update our mix volumes only if it's in use
|
||||
if (in_params.in_use) {
|
||||
mix_volume = in_params.mix_volume;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerVoiceChannelResource::UpdateLastMixVolumes() {
|
||||
last_mix_volume = mix_volume;
|
||||
}
|
||||
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
||||
ServerVoiceChannelResource::GetCurrentMixVolume() const {
|
||||
return mix_volume;
|
||||
}
|
||||
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
|
||||
ServerVoiceChannelResource::GetLastMixVolume() const {
|
||||
return last_mix_volume;
|
||||
}
|
||||
|
||||
ServerVoiceInfo::ServerVoiceInfo() {
|
||||
Initialize();
|
||||
}
|
||||
ServerVoiceInfo::~ServerVoiceInfo() = default;
|
||||
|
||||
void ServerVoiceInfo::Initialize() {
|
||||
in_params.in_use = false;
|
||||
in_params.node_id = 0;
|
||||
in_params.id = 0;
|
||||
in_params.current_playstate = ServerPlayState::Stop;
|
||||
in_params.priority = 255;
|
||||
in_params.sample_rate = 0;
|
||||
in_params.sample_format = SampleFormat::Invalid;
|
||||
in_params.channel_count = 0;
|
||||
in_params.pitch = 0.0f;
|
||||
in_params.volume = 0.0f;
|
||||
in_params.last_volume = 0.0f;
|
||||
in_params.biquad_filter.fill({});
|
||||
in_params.wave_buffer_count = 0;
|
||||
in_params.wave_bufffer_head = 0;
|
||||
in_params.mix_id = AudioCommon::NO_MIX;
|
||||
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
|
||||
in_params.additional_params_address = 0;
|
||||
in_params.additional_params_size = 0;
|
||||
in_params.is_new = false;
|
||||
out_params.played_sample_count = 0;
|
||||
out_params.wave_buffer_consumed = 0;
|
||||
in_params.voice_drop_flag = false;
|
||||
in_params.buffer_mapped = false;
|
||||
in_params.wave_buffer_flush_request_count = 0;
|
||||
in_params.was_biquad_filter_enabled.fill(false);
|
||||
|
||||
for (auto& wave_buffer : in_params.wave_buffer) {
|
||||
wave_buffer.start_sample_offset = 0;
|
||||
wave_buffer.end_sample_offset = 0;
|
||||
wave_buffer.is_looping = false;
|
||||
wave_buffer.end_of_stream = false;
|
||||
wave_buffer.buffer_address = 0;
|
||||
wave_buffer.buffer_size = 0;
|
||||
wave_buffer.context_address = 0;
|
||||
wave_buffer.context_size = 0;
|
||||
wave_buffer.sent_to_dsp = true;
|
||||
}
|
||||
|
||||
stored_samples.clear();
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
|
||||
BehaviorInfo& behavior_info) {
|
||||
in_params.in_use = voice_in.is_in_use;
|
||||
in_params.id = voice_in.id;
|
||||
in_params.node_id = voice_in.node_id;
|
||||
in_params.last_playstate = in_params.current_playstate;
|
||||
switch (voice_in.play_state) {
|
||||
case PlayState::Paused:
|
||||
in_params.current_playstate = ServerPlayState::Paused;
|
||||
break;
|
||||
case PlayState::Stopped:
|
||||
if (in_params.current_playstate != ServerPlayState::Stop) {
|
||||
in_params.current_playstate = ServerPlayState::RequestStop;
|
||||
}
|
||||
break;
|
||||
case PlayState::Started:
|
||||
in_params.current_playstate = ServerPlayState::Play;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
|
||||
break;
|
||||
}
|
||||
|
||||
in_params.priority = voice_in.priority;
|
||||
in_params.sorting_order = voice_in.sorting_order;
|
||||
in_params.sample_rate = voice_in.sample_rate;
|
||||
in_params.sample_format = voice_in.sample_format;
|
||||
in_params.channel_count = voice_in.channel_count;
|
||||
in_params.pitch = voice_in.pitch;
|
||||
in_params.volume = voice_in.volume;
|
||||
in_params.biquad_filter = voice_in.biquad_filter;
|
||||
in_params.wave_buffer_count = voice_in.wave_buffer_count;
|
||||
in_params.wave_bufffer_head = voice_in.wave_buffer_head;
|
||||
if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
|
||||
in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count;
|
||||
}
|
||||
in_params.mix_id = voice_in.mix_id;
|
||||
if (behavior_info.IsSplitterSupported()) {
|
||||
in_params.splitter_info_id = voice_in.splitter_info_id;
|
||||
} else {
|
||||
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
|
||||
}
|
||||
|
||||
std::memcpy(in_params.voice_channel_resource_id.data(),
|
||||
voice_in.voice_channel_resource_ids.data(),
|
||||
sizeof(s32) * in_params.voice_channel_resource_id.size());
|
||||
|
||||
if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
|
||||
in_params.behavior_flags.is_played_samples_reset_at_loop_point =
|
||||
voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
|
||||
} else {
|
||||
in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
|
||||
}
|
||||
if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
|
||||
in_params.behavior_flags.is_pitch_and_src_skipped =
|
||||
voice_in.behavior_flags.is_pitch_and_src_skipped;
|
||||
} else {
|
||||
in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
|
||||
}
|
||||
|
||||
if (voice_in.is_voice_drop_flag_clear_requested) {
|
||||
in_params.voice_drop_flag = false;
|
||||
}
|
||||
|
||||
if (in_params.additional_params_address != voice_in.additional_params_address ||
|
||||
in_params.additional_params_size != voice_in.additional_params_size) {
|
||||
in_params.additional_params_address = voice_in.additional_params_address;
|
||||
in_params.additional_params_size = voice_in.additional_params_size;
|
||||
// TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
|
||||
// our context is new
|
||||
}
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::UpdateWaveBuffers(
|
||||
const VoiceInfo::InParams& voice_in,
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
|
||||
BehaviorInfo& behavior_info) {
|
||||
if (voice_in.is_new) {
|
||||
// Initialize our wave buffers
|
||||
for (auto& wave_buffer : in_params.wave_buffer) {
|
||||
wave_buffer.start_sample_offset = 0;
|
||||
wave_buffer.end_sample_offset = 0;
|
||||
wave_buffer.is_looping = false;
|
||||
wave_buffer.end_of_stream = false;
|
||||
wave_buffer.buffer_address = 0;
|
||||
wave_buffer.buffer_size = 0;
|
||||
wave_buffer.context_address = 0;
|
||||
wave_buffer.context_size = 0;
|
||||
wave_buffer.sent_to_dsp = true;
|
||||
}
|
||||
|
||||
// Mark all our wave buffers as invalid
|
||||
for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
|
||||
channel++) {
|
||||
for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
|
||||
is_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update our wave buffers
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
|
||||
// Assume that we have at least 1 channel voice state
|
||||
const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
|
||||
|
||||
UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
|
||||
have_valid_wave_buffer, behavior_info);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
|
||||
const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
|
||||
bool is_buffer_valid, BehaviorInfo& behavior_info) {
|
||||
if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
|
||||
out_wavebuffer.buffer_address = 0;
|
||||
out_wavebuffer.buffer_size = 0;
|
||||
}
|
||||
|
||||
if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
|
||||
// Validate sample offset sizings
|
||||
if (sample_format == SampleFormat::Pcm16) {
|
||||
const auto buffer_size = in_wave_buffer.buffer_size;
|
||||
if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
|
||||
(buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
|
||||
(buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
|
||||
// TODO(ogniK): Write error info
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO(ogniK): ADPCM Size error
|
||||
|
||||
out_wavebuffer.sent_to_dsp = false;
|
||||
out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
|
||||
out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
|
||||
out_wavebuffer.is_looping = in_wave_buffer.is_looping;
|
||||
out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
|
||||
|
||||
out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
|
||||
out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
|
||||
out_wavebuffer.context_address = in_wave_buffer.context_address;
|
||||
out_wavebuffer.context_size = in_wave_buffer.context_size;
|
||||
in_params.buffer_mapped =
|
||||
in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
|
||||
// TODO(ogniK): Pool mapper attachment
|
||||
// TODO(ogniK): IsAdpcmLoopContextBugFixed
|
||||
}
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::WriteOutStatus(
|
||||
VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
|
||||
if (voice_in.is_new) {
|
||||
in_params.is_new = true;
|
||||
voice_out.wave_buffer_consumed = 0;
|
||||
voice_out.played_sample_count = 0;
|
||||
voice_out.voice_dropped = false;
|
||||
} else if (!in_params.is_new) {
|
||||
voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
|
||||
voice_out.played_sample_count = voice_states[0]->played_sample_count;
|
||||
voice_out.voice_dropped = in_params.voice_drop_flag;
|
||||
} else {
|
||||
voice_out.wave_buffer_consumed = 0;
|
||||
voice_out.played_sample_count = 0;
|
||||
voice_out.voice_dropped = false;
|
||||
}
|
||||
}
|
||||
|
||||
const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
|
||||
return in_params;
|
||||
}
|
||||
|
||||
ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
|
||||
return in_params;
|
||||
}
|
||||
|
||||
const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
|
||||
return out_params;
|
||||
}
|
||||
|
||||
ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
|
||||
return out_params;
|
||||
}
|
||||
|
||||
bool ServerVoiceInfo::ShouldSkip() const {
|
||||
// TODO(ogniK): Handle unmapped wave buffers or parameters
|
||||
return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
|
||||
}
|
||||
|
||||
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
|
||||
if (in_params.is_new) {
|
||||
ResetResources(voice_context);
|
||||
in_params.last_volume = in_params.volume;
|
||||
in_params.is_new = false;
|
||||
}
|
||||
|
||||
const s32 channel_count = in_params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
const auto channel_resource = in_params.voice_channel_resource_id[i];
|
||||
dsp_voice_states[i] =
|
||||
&voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
|
||||
}
|
||||
return UpdateParametersForCommandGeneration(dsp_voice_states);
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
|
||||
const s32 channel_count = in_params.channel_count;
|
||||
for (s32 i = 0; i < channel_count; i++) {
|
||||
const auto channel_resource = in_params.voice_channel_resource_id[i];
|
||||
auto& dsp_state =
|
||||
voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
|
||||
dsp_state = {};
|
||||
voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
|
||||
.UpdateLastMixVolumes();
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
|
||||
const s32 channel_count = in_params.channel_count;
|
||||
if (in_params.wave_buffer_flush_request_count > 0) {
|
||||
FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
|
||||
channel_count);
|
||||
in_params.wave_buffer_flush_request_count = 0;
|
||||
}
|
||||
|
||||
switch (in_params.current_playstate) {
|
||||
case ServerPlayState::Play: {
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
|
||||
if (!in_params.wave_buffer[i].sent_to_dsp) {
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
|
||||
}
|
||||
in_params.wave_buffer[i].sent_to_dsp = true;
|
||||
}
|
||||
}
|
||||
in_params.should_depop = false;
|
||||
return HasValidWaveBuffer(dsp_voice_states[0]);
|
||||
}
|
||||
case ServerPlayState::Paused:
|
||||
case ServerPlayState::Stop: {
|
||||
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
|
||||
return in_params.should_depop;
|
||||
}
|
||||
case ServerPlayState::RequestStop: {
|
||||
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
|
||||
in_params.wave_buffer[i].sent_to_dsp = true;
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
auto* dsp_state = dsp_voice_states[channel];
|
||||
|
||||
if (dsp_state->is_wave_buffer_valid[i]) {
|
||||
dsp_state->wave_buffer_index =
|
||||
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
|
||||
dsp_state->wave_buffer_consumed++;
|
||||
}
|
||||
|
||||
dsp_state->is_wave_buffer_valid[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
auto* dsp_state = dsp_voice_states[channel];
|
||||
dsp_state->offset = 0;
|
||||
dsp_state->played_sample_count = 0;
|
||||
dsp_state->fraction = 0;
|
||||
dsp_state->sample_history.fill(0);
|
||||
dsp_state->context = {};
|
||||
}
|
||||
|
||||
in_params.current_playstate = ServerPlayState::Stop;
|
||||
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
|
||||
return in_params.should_depop;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ServerVoiceInfo::FlushWaveBuffers(
|
||||
u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
|
||||
s32 channel_count) {
|
||||
auto wave_head = in_params.wave_bufffer_head;
|
||||
|
||||
for (u8 i = 0; i < flush_count; i++) {
|
||||
in_params.wave_buffer[wave_head].sent_to_dsp = true;
|
||||
for (s32 channel = 0; channel < channel_count; channel++) {
|
||||
auto* dsp_state = dsp_voice_states[channel];
|
||||
dsp_state->wave_buffer_consumed++;
|
||||
dsp_state->is_wave_buffer_valid[wave_head] = false;
|
||||
dsp_state->wave_buffer_index =
|
||||
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
|
||||
}
|
||||
wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
|
||||
const auto& valid_wb = state->is_wave_buffer_valid;
|
||||
return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
|
||||
}
|
||||
|
||||
VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
voice_channel_resources.emplace_back(static_cast<s32>(i));
|
||||
sorted_voice_info.push_back(&voice_info.emplace_back());
|
||||
voice_states.emplace_back();
|
||||
dsp_voice_states.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
VoiceContext::~VoiceContext() {
|
||||
sorted_voice_info.clear();
|
||||
}
|
||||
|
||||
std::size_t VoiceContext::GetVoiceCount() const {
|
||||
return voice_count;
|
||||
}
|
||||
|
||||
ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_channel_resources.at(i);
|
||||
}
|
||||
|
||||
const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_channel_resources.at(i);
|
||||
}
|
||||
|
||||
VoiceState& VoiceContext::GetState(std::size_t i) {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_states.at(i);
|
||||
}
|
||||
|
||||
const VoiceState& VoiceContext::GetState(std::size_t i) const {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_states.at(i);
|
||||
}
|
||||
|
||||
VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
|
||||
ASSERT(i < voice_count);
|
||||
return dsp_voice_states.at(i);
|
||||
}
|
||||
|
||||
const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
|
||||
ASSERT(i < voice_count);
|
||||
return dsp_voice_states.at(i);
|
||||
}
|
||||
|
||||
ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_info.at(i);
|
||||
}
|
||||
|
||||
const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
|
||||
ASSERT(i < voice_count);
|
||||
return voice_info.at(i);
|
||||
}
|
||||
|
||||
ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
|
||||
ASSERT(i < voice_count);
|
||||
return *sorted_voice_info.at(i);
|
||||
}
|
||||
|
||||
const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
|
||||
ASSERT(i < voice_count);
|
||||
return *sorted_voice_info.at(i);
|
||||
}
|
||||
|
||||
s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
|
||||
s32 channel_count, s32 buffer_offset, s32 sample_count,
|
||||
Core::Memory::Memory& memory) {
|
||||
if (wave_buffer->buffer_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer->buffer_size == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto samples_remaining =
|
||||
(wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
|
||||
const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
|
||||
const auto buffer_pos = wave_buffer->buffer_address + start_offset;
|
||||
|
||||
s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
|
||||
|
||||
const auto samples_processed = std::min(sample_count, samples_remaining);
|
||||
|
||||
// Fast path
|
||||
if (channel_count == 1) {
|
||||
for (std::size_t i = 0; i < samples_processed; i++) {
|
||||
output_buffer[i] = buffer_data[i];
|
||||
}
|
||||
} else {
|
||||
for (std::size_t i = 0; i < samples_processed; i++) {
|
||||
output_buffer[i] = buffer_data[i * channel_count + channel];
|
||||
}
|
||||
}
|
||||
|
||||
return samples_processed;
|
||||
}
|
||||
|
||||
void VoiceContext::SortInfo() {
|
||||
for (std::size_t i = 0; i < voice_count; i++) {
|
||||
sorted_voice_info[i] = &voice_info[i];
|
||||
}
|
||||
|
||||
std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
|
||||
[](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
|
||||
const auto& lhs_in = lhs->GetInParams();
|
||||
const auto& rhs_in = rhs->GetInParams();
|
||||
// Sort by priority
|
||||
if (lhs_in.priority != rhs_in.priority) {
|
||||
return lhs_in.priority > rhs_in.priority;
|
||||
} else {
|
||||
// If the priorities match, sort by sorting order
|
||||
return lhs_in.sorting_order > rhs_in.sorting_order;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void VoiceContext::UpdateStateByDspShared() {
|
||||
voice_states = dsp_voice_states;
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "audio_core/algorithm/interpolate.h"
|
||||
#include "audio_core/codec.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class BehaviorInfo;
|
||||
class VoiceContext;
|
||||
|
||||
enum class SampleFormat : u8 {
|
||||
Invalid = 0,
|
||||
Pcm8 = 1,
|
||||
Pcm16 = 2,
|
||||
Pcm24 = 3,
|
||||
Pcm32 = 4,
|
||||
PcmFloat = 5,
|
||||
Adpcm = 6,
|
||||
};
|
||||
|
||||
enum class PlayState : u8 {
|
||||
Started = 0,
|
||||
Stopped = 1,
|
||||
Paused = 2,
|
||||
};
|
||||
|
||||
enum class ServerPlayState {
|
||||
Play = 0,
|
||||
Stop = 1,
|
||||
RequestStop = 2,
|
||||
Paused = 3,
|
||||
};
|
||||
|
||||
struct BiquadFilterParameter {
|
||||
bool enabled{};
|
||||
INSERT_PADDING_BYTES(1);
|
||||
std::array<s16, 3> numerator{};
|
||||
std::array<s16, 2> denominator{};
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
|
||||
|
||||
struct WaveBuffer {
|
||||
u64_le buffer_address{};
|
||||
u64_le buffer_size{};
|
||||
s32_le start_sample_offset{};
|
||||
s32_le end_sample_offset{};
|
||||
u8 is_looping{};
|
||||
u8 end_of_stream{};
|
||||
u8 sent_to_server{};
|
||||
INSERT_PADDING_BYTES(5);
|
||||
u64 context_address{};
|
||||
u64 context_size{};
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
|
||||
|
||||
struct ServerWaveBuffer {
|
||||
VAddr buffer_address{};
|
||||
std::size_t buffer_size{};
|
||||
s32 start_sample_offset{};
|
||||
s32 end_sample_offset{};
|
||||
bool is_looping{};
|
||||
bool end_of_stream{};
|
||||
VAddr context_address{};
|
||||
std::size_t context_size{};
|
||||
bool sent_to_dsp{true};
|
||||
};
|
||||
|
||||
struct BehaviorFlags {
|
||||
BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
|
||||
BitField<1, 1, u16> is_pitch_and_src_skipped;
|
||||
};
|
||||
static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
|
||||
|
||||
struct ADPCMContext {
|
||||
u16 header{};
|
||||
s16 yn1{};
|
||||
s16 yn2{};
|
||||
};
|
||||
static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
|
||||
|
||||
struct VoiceState {
|
||||
s64 played_sample_count{};
|
||||
s32 offset{};
|
||||
s32 wave_buffer_index{};
|
||||
std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
|
||||
s32 wave_buffer_consumed{};
|
||||
std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
|
||||
s32 fraction{};
|
||||
VAddr context_address{};
|
||||
Codec::ADPCM_Coeff coeff{};
|
||||
ADPCMContext context{};
|
||||
std::array<s64, 2> biquad_filter_state{};
|
||||
std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
|
||||
u32 external_context_size{};
|
||||
bool is_external_context_used{};
|
||||
bool voice_dropped{};
|
||||
};
|
||||
|
||||
class VoiceChannelResource {
|
||||
public:
|
||||
struct InParams {
|
||||
s32_le id{};
|
||||
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(11);
|
||||
};
|
||||
static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
|
||||
};
|
||||
|
||||
class ServerVoiceChannelResource {
|
||||
public:
|
||||
explicit ServerVoiceChannelResource(s32 id);
|
||||
~ServerVoiceChannelResource();
|
||||
|
||||
bool InUse() const;
|
||||
float GetCurrentMixVolumeAt(std::size_t i) const;
|
||||
float GetLastMixVolumeAt(std::size_t i) const;
|
||||
void Update(VoiceChannelResource::InParams& in_params);
|
||||
void UpdateLastMixVolumes();
|
||||
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
|
||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
|
||||
|
||||
private:
|
||||
s32 id{};
|
||||
std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
|
||||
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
|
||||
bool in_use{};
|
||||
};
|
||||
|
||||
class VoiceInfo {
|
||||
public:
|
||||
struct InParams {
|
||||
s32_le id{};
|
||||
u32_le node_id{};
|
||||
u8 is_new{};
|
||||
u8 is_in_use{};
|
||||
PlayState play_state{};
|
||||
SampleFormat sample_format{};
|
||||
s32_le sample_rate{};
|
||||
s32_le priority{};
|
||||
s32_le sorting_order{};
|
||||
s32_le channel_count{};
|
||||
float_le pitch{};
|
||||
float_le volume{};
|
||||
std::array<BiquadFilterParameter, 2> biquad_filter{};
|
||||
s32_le wave_buffer_count{};
|
||||
s16_le wave_buffer_head{};
|
||||
INSERT_PADDING_BYTES(6);
|
||||
u64_le additional_params_address{};
|
||||
u64_le additional_params_size{};
|
||||
s32_le mix_id{};
|
||||
s32_le splitter_info_id{};
|
||||
std::array<WaveBuffer, 4> wave_buffer{};
|
||||
std::array<u32_le, 6> voice_channel_resource_ids{};
|
||||
// TODO(ogniK): Remaining flags
|
||||
u8 is_voice_drop_flag_clear_requested{};
|
||||
u8 wave_buffer_flush_request_count{};
|
||||
INSERT_PADDING_BYTES(2);
|
||||
BehaviorFlags behavior_flags{};
|
||||
INSERT_PADDING_BYTES(16);
|
||||
};
|
||||
static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
|
||||
|
||||
struct OutParams {
|
||||
u64_le played_sample_count{};
|
||||
u32_le wave_buffer_consumed{};
|
||||
u8 voice_dropped{};
|
||||
INSERT_PADDING_BYTES(3);
|
||||
};
|
||||
static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
|
||||
};
|
||||
|
||||
class ServerVoiceInfo {
|
||||
public:
|
||||
struct InParams {
|
||||
bool in_use{};
|
||||
bool is_new{};
|
||||
bool should_depop{};
|
||||
SampleFormat sample_format{};
|
||||
s32 sample_rate{};
|
||||
s32 channel_count{};
|
||||
s32 id{};
|
||||
s32 node_id{};
|
||||
s32 mix_id{};
|
||||
ServerPlayState current_playstate{};
|
||||
ServerPlayState last_playstate{};
|
||||
s32 priority{};
|
||||
s32 sorting_order{};
|
||||
float pitch{};
|
||||
float volume{};
|
||||
float last_volume{};
|
||||
std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
|
||||
s32 wave_buffer_count{};
|
||||
s16 wave_bufffer_head{};
|
||||
INSERT_PADDING_BYTES(2);
|
||||
BehaviorFlags behavior_flags{};
|
||||
VAddr additional_params_address{};
|
||||
std::size_t additional_params_size{};
|
||||
std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
|
||||
std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
|
||||
s32 splitter_info_id{};
|
||||
u8 wave_buffer_flush_request_count{};
|
||||
bool voice_drop_flag{};
|
||||
bool buffer_mapped{};
|
||||
std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
|
||||
};
|
||||
|
||||
struct OutParams {
|
||||
s64 played_sample_count{};
|
||||
s32 wave_buffer_consumed{};
|
||||
};
|
||||
|
||||
ServerVoiceInfo();
|
||||
~ServerVoiceInfo();
|
||||
void Initialize();
|
||||
void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
|
||||
void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
|
||||
BehaviorInfo& behavior_info);
|
||||
void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
|
||||
SampleFormat sample_format, bool is_buffer_valid,
|
||||
BehaviorInfo& behavior_info);
|
||||
void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
|
||||
|
||||
const InParams& GetInParams() const;
|
||||
InParams& GetInParams();
|
||||
|
||||
const OutParams& GetOutParams() const;
|
||||
OutParams& GetOutParams();
|
||||
|
||||
bool ShouldSkip() const;
|
||||
bool UpdateForCommandGeneration(VoiceContext& voice_context);
|
||||
void ResetResources(VoiceContext& voice_context);
|
||||
bool UpdateParametersForCommandGeneration(
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
|
||||
void FlushWaveBuffers(u8 flush_count,
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
|
||||
s32 channel_count);
|
||||
|
||||
private:
|
||||
std::vector<s16> stored_samples;
|
||||
InParams in_params{};
|
||||
OutParams out_params{};
|
||||
|
||||
bool HasValidWaveBuffer(const VoiceState* state) const;
|
||||
};
|
||||
|
||||
class VoiceContext {
|
||||
public:
|
||||
VoiceContext(std::size_t voice_count);
|
||||
~VoiceContext();
|
||||
|
||||
std::size_t GetVoiceCount() const;
|
||||
ServerVoiceChannelResource& GetChannelResource(std::size_t i);
|
||||
const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
|
||||
VoiceState& GetState(std::size_t i);
|
||||
const VoiceState& GetState(std::size_t i) const;
|
||||
VoiceState& GetDspSharedState(std::size_t i);
|
||||
const VoiceState& GetDspSharedState(std::size_t i) const;
|
||||
ServerVoiceInfo& GetInfo(std::size_t i);
|
||||
const ServerVoiceInfo& GetInfo(std::size_t i) const;
|
||||
ServerVoiceInfo& GetSortedInfo(std::size_t i);
|
||||
const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
|
||||
|
||||
s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
|
||||
s32 channel_count, s32 buffer_offset, s32 sample_count,
|
||||
Core::Memory::Memory& memory);
|
||||
void SortInfo();
|
||||
void UpdateStateByDspShared();
|
||||
|
||||
private:
|
||||
std::size_t voice_count{};
|
||||
std::vector<ServerVoiceChannelResource> voice_channel_resources{};
|
||||
std::vector<VoiceState> voice_states{};
|
||||
std::vector<VoiceState> dsp_voice_states{};
|
||||
std::vector<ServerVoiceInfo> voice_info{};
|
||||
std::vector<ServerVoiceInfo*> sorted_voice_info{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -495,6 +495,7 @@ add_library(core STATIC
|
||||
hle/service/sm/controller.h
|
||||
hle/service/sm/sm.cpp
|
||||
hle/service/sm/sm.h
|
||||
hle/service/sockets/blocking_worker.h
|
||||
hle/service/sockets/bsd.cpp
|
||||
hle/service/sockets/bsd.h
|
||||
hle/service/sockets/ethc.cpp
|
||||
@@ -505,6 +506,8 @@ add_library(core STATIC
|
||||
hle/service/sockets/sfdnsres.h
|
||||
hle/service/sockets/sockets.cpp
|
||||
hle/service/sockets/sockets.h
|
||||
hle/service/sockets/sockets_translate.cpp
|
||||
hle/service/sockets/sockets_translate.h
|
||||
hle/service/spl/csrng.cpp
|
||||
hle/service/spl/csrng.h
|
||||
hle/service/spl/module.cpp
|
||||
|
||||
@@ -188,7 +188,6 @@ struct System::Impl {
|
||||
if (!gpu_core) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
gpu_core->Renderer().Rasterizer().SetupDirtyFlags();
|
||||
|
||||
is_powered_on = true;
|
||||
exit_lock = false;
|
||||
|
||||
@@ -188,7 +188,7 @@ private:
|
||||
|
||||
/// Scheduler lock mechanisms.
|
||||
bool is_locked{};
|
||||
Common::SpinLock inner_lock{};
|
||||
std::mutex inner_lock;
|
||||
std::atomic<s64> scope_lock{};
|
||||
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Service::Audio {
|
||||
|
||||
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
|
||||
public:
|
||||
explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
|
||||
explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
|
||||
const std::size_t instance_number)
|
||||
: ServiceFramework("IAudioRenderer") {
|
||||
// clang-format off
|
||||
@@ -94,14 +94,15 @@ private:
|
||||
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "(STUBBED) called");
|
||||
|
||||
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer());
|
||||
std::vector<u8> output_params(ctx.GetWriteBufferSize());
|
||||
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
|
||||
|
||||
if (result.Succeeded()) {
|
||||
ctx.WriteBuffer(result.Unwrap());
|
||||
if (result.IsSuccess()) {
|
||||
ctx.WriteBuffer(output_params);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result.Code());
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void Start(Kernel::HLERequestContext& ctx) {
|
||||
@@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
|
||||
OpenAudioRendererImpl(ctx);
|
||||
}
|
||||
|
||||
static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
|
||||
static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
|
||||
// +1 represents the final mix.
|
||||
return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
|
||||
1;
|
||||
@@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
constexpr u64 upsampler_manager_size = 0x48;
|
||||
|
||||
// Calculates the part of the size that relates to mix buffers.
|
||||
const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
// As of 8.0.0 this is the maximum on voice channels.
|
||||
constexpr u64 max_voice_channels = 6;
|
||||
|
||||
@@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the portion of the size related to the mix data (and the sorting thereof).
|
||||
const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
// The size of the mixing info data structure.
|
||||
constexpr u64 mix_info_size = 0x940;
|
||||
|
||||
@@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to voice channel info.
|
||||
const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
constexpr u64 voice_info_size = 0x220;
|
||||
constexpr u64 voice_resource_size = 0xD0;
|
||||
|
||||
@@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to memory pools.
|
||||
const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
|
||||
const u64 memory_pool_info_size = 0x20;
|
||||
return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
|
||||
@@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
// Calculates the part of the size related to the splitter context.
|
||||
const auto calculate_splitter_context_size =
|
||||
[](const AudioCore::AudioRendererParameter& params) -> u64 {
|
||||
[](const AudioCommon::AudioRendererParameter& params) -> u64 {
|
||||
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to the upsampler info.
|
||||
const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
constexpr u64 upsampler_info_size = 0x280;
|
||||
// Yes, using the buffer size over info alignment size is intentional here.
|
||||
return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
|
||||
buffer_alignment_size);
|
||||
};
|
||||
const auto calculate_upsampler_info_size =
|
||||
[](const AudioCommon::AudioRendererParameter& params) {
|
||||
constexpr u64 upsampler_info_size = 0x280;
|
||||
// Yes, using the buffer size over info alignment size is intentional here.
|
||||
return Common::AlignUp(upsampler_info_size *
|
||||
(u64{params.submix_count} + params.sink_count),
|
||||
buffer_alignment_size);
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to effect info.
|
||||
const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
constexpr u64 effect_info_size = 0x2B0;
|
||||
return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to audio sink info.
|
||||
const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
const u64 sink_info_size = 0x170;
|
||||
return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to voice state info.
|
||||
const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
const u64 voice_state_size = 0x100;
|
||||
const u64 additional_size = buffer_alignment_size - 1;
|
||||
return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
|
||||
@@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the part of the size related to performance statistics.
|
||||
const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
|
||||
// Extra size value appended to the end of the calculation.
|
||||
constexpr u64 appended = 128;
|
||||
|
||||
@@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
};
|
||||
|
||||
// Calculates the part of the size that relates to the audio command buffer.
|
||||
const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
|
||||
constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
|
||||
const auto calculate_command_buffer_size =
|
||||
[](const AudioCommon::AudioRendererParameter& params) {
|
||||
constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
|
||||
|
||||
if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
|
||||
constexpr u64 command_buffer_size = 0x18000;
|
||||
if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
|
||||
constexpr u64 command_buffer_size = 0x18000;
|
||||
|
||||
return command_buffer_size + alignment;
|
||||
}
|
||||
return command_buffer_size + alignment;
|
||||
}
|
||||
|
||||
// When the variadic command buffer is supported, this means
|
||||
// the command generator for the audio renderer can issue commands
|
||||
// that are (as one would expect), variable in size. So what we need to do
|
||||
// is determine the maximum possible size for a few command data structures
|
||||
// then multiply them by the amount of present commands indicated by the given
|
||||
// respective audio parameters.
|
||||
// When the variadic command buffer is supported, this means
|
||||
// the command generator for the audio renderer can issue commands
|
||||
// that are (as one would expect), variable in size. So what we need to do
|
||||
// is determine the maximum possible size for a few command data structures
|
||||
// then multiply them by the amount of present commands indicated by the given
|
||||
// respective audio parameters.
|
||||
|
||||
constexpr u64 max_biquad_filters = 2;
|
||||
constexpr u64 max_mix_buffers = 24;
|
||||
constexpr u64 max_biquad_filters = 2;
|
||||
constexpr u64 max_mix_buffers = 24;
|
||||
|
||||
constexpr u64 biquad_filter_command_size = 0x2C;
|
||||
constexpr u64 biquad_filter_command_size = 0x2C;
|
||||
|
||||
constexpr u64 depop_mix_command_size = 0x24;
|
||||
constexpr u64 depop_setup_command_size = 0x50;
|
||||
constexpr u64 depop_mix_command_size = 0x24;
|
||||
constexpr u64 depop_setup_command_size = 0x50;
|
||||
|
||||
constexpr u64 effect_command_max_size = 0x540;
|
||||
constexpr u64 effect_command_max_size = 0x540;
|
||||
|
||||
constexpr u64 mix_command_size = 0x1C;
|
||||
constexpr u64 mix_ramp_command_size = 0x24;
|
||||
constexpr u64 mix_ramp_grouped_command_size = 0x13C;
|
||||
constexpr u64 mix_command_size = 0x1C;
|
||||
constexpr u64 mix_ramp_command_size = 0x24;
|
||||
constexpr u64 mix_ramp_grouped_command_size = 0x13C;
|
||||
|
||||
constexpr u64 perf_command_size = 0x28;
|
||||
constexpr u64 perf_command_size = 0x28;
|
||||
|
||||
constexpr u64 sink_command_size = 0x130;
|
||||
constexpr u64 sink_command_size = 0x130;
|
||||
|
||||
constexpr u64 submix_command_max_size =
|
||||
depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
|
||||
constexpr u64 submix_command_max_size =
|
||||
depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
|
||||
|
||||
constexpr u64 volume_command_size = 0x1C;
|
||||
constexpr u64 volume_ramp_command_size = 0x20;
|
||||
constexpr u64 volume_command_size = 0x1C;
|
||||
constexpr u64 volume_ramp_command_size = 0x20;
|
||||
|
||||
constexpr u64 voice_biquad_filter_command_size =
|
||||
biquad_filter_command_size * max_biquad_filters;
|
||||
constexpr u64 voice_data_command_size = 0x9C;
|
||||
const u64 voice_command_max_size =
|
||||
(params.splitter_count * depop_setup_command_size) +
|
||||
(voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
|
||||
mix_ramp_grouped_command_size);
|
||||
constexpr u64 voice_biquad_filter_command_size =
|
||||
biquad_filter_command_size * max_biquad_filters;
|
||||
constexpr u64 voice_data_command_size = 0x9C;
|
||||
const u64 voice_command_max_size =
|
||||
(params.splitter_count * depop_setup_command_size) +
|
||||
(voice_data_command_size + voice_biquad_filter_command_size +
|
||||
volume_ramp_command_size + mix_ramp_grouped_command_size);
|
||||
|
||||
// Now calculate the individual elements that comprise the size and add them together.
|
||||
const u64 effect_commands_size = params.effect_count * effect_command_max_size;
|
||||
// Now calculate the individual elements that comprise the size and add them together.
|
||||
const u64 effect_commands_size = params.effect_count * effect_command_max_size;
|
||||
|
||||
const u64 final_mix_commands_size =
|
||||
depop_mix_command_size + volume_command_size * max_mix_buffers;
|
||||
const u64 final_mix_commands_size =
|
||||
depop_mix_command_size + volume_command_size * max_mix_buffers;
|
||||
|
||||
const u64 perf_commands_size =
|
||||
perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
|
||||
const u64 perf_commands_size =
|
||||
perf_command_size *
|
||||
(CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
|
||||
|
||||
const u64 sink_commands_size = params.sink_count * sink_command_size;
|
||||
const u64 sink_commands_size = params.sink_count * sink_command_size;
|
||||
|
||||
const u64 splitter_commands_size =
|
||||
params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
|
||||
const u64 splitter_commands_size =
|
||||
params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
|
||||
|
||||
const u64 submix_commands_size = params.submix_count * submix_command_max_size;
|
||||
const u64 submix_commands_size = params.submix_count * submix_command_max_size;
|
||||
|
||||
const u64 voice_commands_size = params.voice_count * voice_command_max_size;
|
||||
const u64 voice_commands_size = params.voice_count * voice_command_max_size;
|
||||
|
||||
return effect_commands_size + final_mix_commands_size + perf_commands_size +
|
||||
sink_commands_size + splitter_commands_size + submix_commands_size +
|
||||
voice_commands_size + alignment;
|
||||
};
|
||||
return effect_commands_size + final_mix_commands_size + perf_commands_size +
|
||||
sink_commands_size + splitter_commands_size + submix_commands_size +
|
||||
voice_commands_size + alignment;
|
||||
};
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
|
||||
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
|
||||
|
||||
u64 size = 0;
|
||||
size += calculate_mix_buffer_sizes(params);
|
||||
@@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
|
||||
|
||||
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
|
||||
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -495,6 +495,14 @@ Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
|
||||
return hold_type;
|
||||
}
|
||||
|
||||
void Controller_NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode) {
|
||||
handheld_activation_mode = activation_mode;
|
||||
}
|
||||
|
||||
Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActivationMode() const {
|
||||
return handheld_activation_mode;
|
||||
}
|
||||
|
||||
void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
|
||||
const std::size_t npad_index = NPadIdToIndex(npad_id);
|
||||
ASSERT(npad_index < shared_memory_entries.size());
|
||||
@@ -582,6 +590,22 @@ Controller_NPad::GyroscopeZeroDriftMode Controller_NPad::GetGyroscopeZeroDriftMo
|
||||
return gyroscope_zero_drift_mode;
|
||||
}
|
||||
|
||||
void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
|
||||
const auto npad_index_1 = NPadIdToIndex(npad_id_1);
|
||||
const auto npad_index_2 = NPadIdToIndex(npad_id_2);
|
||||
|
||||
// If the controllers at both npad indices form a pair of left and right joycons, merge them.
|
||||
// Otherwise, do nothing.
|
||||
if ((connected_controllers[npad_index_1].type == NPadControllerType::JoyLeft &&
|
||||
connected_controllers[npad_index_2].type == NPadControllerType::JoyRight) ||
|
||||
(connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
|
||||
connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
|
||||
// Disconnect the joycon at the second id and connect the dual joycon at the first index.
|
||||
DisconnectNPad(npad_id_2);
|
||||
AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller_NPad::StartLRAssignmentMode() {
|
||||
// Nothing internally is used for lr assignment mode. Since we have the ability to set the
|
||||
// controller types from boot, it doesn't really matter about showing a selection screen
|
||||
|
||||
@@ -74,6 +74,12 @@ public:
|
||||
Single = 1,
|
||||
};
|
||||
|
||||
enum class NpadHandheldActivationMode : u64 {
|
||||
Dual = 0,
|
||||
Single = 1,
|
||||
None = 2,
|
||||
};
|
||||
|
||||
enum class NPadControllerType {
|
||||
None,
|
||||
ProController,
|
||||
@@ -110,6 +116,9 @@ public:
|
||||
void SetHoldType(NpadHoldType joy_hold_type);
|
||||
NpadHoldType GetHoldType() const;
|
||||
|
||||
void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
|
||||
NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
|
||||
|
||||
void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
|
||||
|
||||
void VibrateController(const std::vector<u32>& controller_ids,
|
||||
@@ -138,6 +147,7 @@ public:
|
||||
void ConnectAllDisconnectedControllers();
|
||||
void ClearAllControllers();
|
||||
|
||||
void MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2);
|
||||
void StartLRAssignmentMode();
|
||||
void StopLRAssignmentMode();
|
||||
bool SwapNpadAssignment(u32 npad_id_1, u32 npad_id_2);
|
||||
@@ -334,6 +344,7 @@ private:
|
||||
sticks;
|
||||
std::vector<u32> supported_npad_id_types{};
|
||||
NpadHoldType hold_type{NpadHoldType::Vertical};
|
||||
NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
|
||||
// Each controller should have their own styleset changed event
|
||||
std::array<Kernel::EventPair, 10> styleset_changed_events;
|
||||
Vibration last_processed_vibration{};
|
||||
|
||||
@@ -671,13 +671,15 @@ void Hid::SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void Hid::MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto unknown_1{rp.Pop<u32>()};
|
||||
const auto unknown_2{rp.Pop<u32>()};
|
||||
const auto npad_id_1{rp.Pop<u32>()};
|
||||
const auto npad_id_2{rp.Pop<u32>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID,
|
||||
"(STUBBED) called, unknown_1={}, unknown_2={}, applet_resource_user_id={}",
|
||||
unknown_1, unknown_2, applet_resource_user_id);
|
||||
LOG_DEBUG(Service_HID, "called, npad_id_1={}, npad_id_2={}, applet_resource_user_id={}",
|
||||
npad_id_1, npad_id_2, applet_resource_user_id);
|
||||
|
||||
auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
|
||||
controller.MergeSingleJoyAsDualJoy(npad_id_1, npad_id_2);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -712,8 +714,11 @@ void Hid::SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
const auto mode{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}, mode={}",
|
||||
applet_resource_user_id, mode);
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}, mode={}", applet_resource_user_id,
|
||||
mode);
|
||||
|
||||
applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.SetNpadHandheldActivationMode(Controller_NPad::NpadHandheldActivationMode{mode});
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -723,11 +728,13 @@ void Hid::GetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_HID, "(STUBBED) called, applet_resource_user_id={}",
|
||||
applet_resource_user_id);
|
||||
LOG_DEBUG(Service_HID, "called, applet_resource_user_id={}", applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u64>(
|
||||
static_cast<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
|
||||
.GetNpadHandheldActivationMode()));
|
||||
}
|
||||
|
||||
void Hid::SwapNpadAssignment(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/nifm/nifm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::NIFM {
|
||||
@@ -174,6 +175,16 @@ private:
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
void GetCurrentIpAddress(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
|
||||
const auto [ipv4, error] = Network::GetHostIPv4Address();
|
||||
UNIMPLEMENTED_IF(error != Network::Errno::SUCCESS);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(ipv4);
|
||||
}
|
||||
void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NIFM, "called");
|
||||
|
||||
@@ -235,7 +246,7 @@ IGeneralService::IGeneralService(Core::System& system)
|
||||
{9, nullptr, "SetNetworkProfile"},
|
||||
{10, &IGeneralService::RemoveNetworkProfile, "RemoveNetworkProfile"},
|
||||
{11, nullptr, "GetScanDataOld"},
|
||||
{12, nullptr, "GetCurrentIpAddress"},
|
||||
{12, &IGeneralService::GetCurrentIpAddress, "GetCurrentIpAddress"},
|
||||
{13, nullptr, "GetCurrentAccessPointOld"},
|
||||
{14, &IGeneralService::CreateTemporaryNetworkProfile, "CreateTemporaryNetworkProfile"},
|
||||
{15, nullptr, "GetCurrentIpConfigInfo"},
|
||||
|
||||
@@ -246,7 +246,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
|
||||
PSC::InstallInterfaces(*sm);
|
||||
PSM::InstallInterfaces(*sm);
|
||||
Set::InstallInterfaces(*sm);
|
||||
Sockets::InstallInterfaces(*sm);
|
||||
Sockets::InstallInterfaces(*sm, system);
|
||||
SPL::InstallInterfaces(*sm);
|
||||
SSL::InstallInterfaces(*sm);
|
||||
Time::InstallInterfaces(system);
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
/**
|
||||
* Worker abstraction to execute blocking calls on host without blocking the guest thread
|
||||
*
|
||||
* @tparam Service Service where the work is executed
|
||||
* @tparam Types Types of work to execute
|
||||
*/
|
||||
template <class Service, class... Types>
|
||||
class BlockingWorker {
|
||||
using This = BlockingWorker<Service, Types...>;
|
||||
using WorkVariant = std::variant<std::monostate, Types...>;
|
||||
|
||||
public:
|
||||
/// Create a new worker
|
||||
static std::unique_ptr<This> Create(Core::System& system, Service* service,
|
||||
std::string_view name) {
|
||||
return std::unique_ptr<This>(new This(system, service, name));
|
||||
}
|
||||
|
||||
~BlockingWorker() {
|
||||
while (!is_available.load(std::memory_order_relaxed)) {
|
||||
// Busy wait until work is finished
|
||||
std::this_thread::yield();
|
||||
}
|
||||
// Monostate means to exit the thread
|
||||
work = std::monostate{};
|
||||
work_event.Set();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to capture the worker to send work after a success
|
||||
* @returns True when the worker has been successfully captured
|
||||
*/
|
||||
bool TryCapture() {
|
||||
bool expected = true;
|
||||
return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send work to this worker abstraction
|
||||
* @see TryCapture must be called before attempting to call this function
|
||||
*/
|
||||
template <class Work>
|
||||
void SendWork(Work new_work) {
|
||||
ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured");
|
||||
work = std::move(new_work);
|
||||
work_event.Set();
|
||||
}
|
||||
|
||||
/// Generate a callback for @see SleepClientThread
|
||||
template <class Work>
|
||||
auto Callback() {
|
||||
return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
|
||||
Kernel::ThreadWakeupReason reason) {
|
||||
ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
|
||||
std::get<Work>(work).Response(ctx);
|
||||
is_available.store(true);
|
||||
};
|
||||
}
|
||||
|
||||
/// Get kernel event that will be signalled by the worker when the host operation finishes
|
||||
std::shared_ptr<Kernel::WritableEvent> KernelEvent() const {
|
||||
return kernel_event;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) {
|
||||
auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name));
|
||||
kernel_event = std::move(pair.writable);
|
||||
thread = std::thread([this, &system, service, name] { Run(system, service, name); });
|
||||
}
|
||||
|
||||
void Run(Core::System& system, Service* service, std::string_view name) {
|
||||
system.RegisterHostThread();
|
||||
|
||||
const std::string thread_name = fmt::format("yuzu:{}", name);
|
||||
MicroProfileOnThreadCreate(thread_name.c_str());
|
||||
Common::SetCurrentThreadName(thread_name.c_str());
|
||||
|
||||
bool keep_running = true;
|
||||
while (keep_running) {
|
||||
work_event.Wait();
|
||||
|
||||
const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
|
||||
if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
|
||||
keep_running = false;
|
||||
} else {
|
||||
w.Execute(service);
|
||||
}
|
||||
};
|
||||
std::visit(visit_fn, work);
|
||||
|
||||
kernel_event->Signal();
|
||||
}
|
||||
}
|
||||
|
||||
std::thread thread;
|
||||
WorkVariant work;
|
||||
Common::Event work_event;
|
||||
std::shared_ptr<Kernel::WritableEvent> kernel_event;
|
||||
std::atomic_bool is_available{true};
|
||||
};
|
||||
|
||||
template <class Service, class... Types>
|
||||
class BlockingWorkerPool {
|
||||
using Worker = BlockingWorker<Service, Types...>;
|
||||
|
||||
public:
|
||||
explicit BlockingWorkerPool(Core::System& system_, Service* service_)
|
||||
: system{system_}, service{service_} {}
|
||||
|
||||
/// Returns a captured worker thread, creating new ones if necessary
|
||||
Worker* CaptureWorker() {
|
||||
for (auto& worker : workers) {
|
||||
if (worker->TryCapture()) {
|
||||
return worker.get();
|
||||
}
|
||||
}
|
||||
auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size()));
|
||||
[[maybe_unused]] const bool success = new_worker->TryCapture();
|
||||
ASSERT(success);
|
||||
|
||||
return workers.emplace_back(std::move(new_worker)).get();
|
||||
}
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
Service* const service;
|
||||
|
||||
std::vector<std::unique_ptr<Worker>> workers;
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
@@ -2,18 +2,138 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/sockets/bsd.h"
|
||||
#include "core/hle/service/sockets/sockets_translate.h"
|
||||
#include "core/network/network.h"
|
||||
#include "core/network/sockets.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsConnectionBased(Type type) {
|
||||
switch (type) {
|
||||
case Type::STREAM:
|
||||
return true;
|
||||
case Type::DGRAM:
|
||||
return false;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void BSD::PollWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->PollImpl(write_buffer, read_buffer, nfds, timeout);
|
||||
}
|
||||
|
||||
void BSD::PollWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(write_buffer);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::AcceptWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->AcceptImpl(fd, write_buffer);
|
||||
}
|
||||
|
||||
void BSD::AcceptWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(write_buffer);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
rb.Push<u32>(static_cast<u32>(write_buffer.size()));
|
||||
}
|
||||
|
||||
void BSD::ConnectWork::Execute(BSD* bsd) {
|
||||
bsd_errno = bsd->ConnectImpl(fd, addr);
|
||||
}
|
||||
|
||||
void BSD::ConnectWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::RecvWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->RecvImpl(fd, flags, message);
|
||||
}
|
||||
|
||||
void BSD::RecvWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(message);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::RecvFromWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->RecvFromImpl(fd, flags, message, addr);
|
||||
}
|
||||
|
||||
void BSD::RecvFromWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(message, 0);
|
||||
if (!addr.empty()) {
|
||||
ctx.WriteBuffer(addr, 1);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
rb.Push<u32>(static_cast<u32>(addr.size()));
|
||||
}
|
||||
|
||||
void BSD::SendWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->SendImpl(fd, flags, message);
|
||||
}
|
||||
|
||||
void BSD::SendWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::SendToWork::Execute(BSD* bsd) {
|
||||
std::tie(ret, bsd_errno) = bsd->SendToImpl(fd, flags, message, addr);
|
||||
}
|
||||
|
||||
void BSD::SendToWork::Response(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::RegisterClient(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
@@ -26,20 +146,19 @@ void BSD::StartMonitoring(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void BSD::Socket(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u32 domain = rp.Pop<u32>();
|
||||
const u32 type = rp.Pop<u32>();
|
||||
const u32 protocol = rp.Pop<u32>();
|
||||
|
||||
u32 domain = rp.Pop<u32>();
|
||||
u32 type = rp.Pop<u32>();
|
||||
u32 protocol = rp.Pop<u32>();
|
||||
LOG_DEBUG(Service, "called. domain={} type={} protocol={}", domain, type, protocol);
|
||||
|
||||
LOG_WARNING(Service, "(STUBBED) called domain={} type={} protocol={}", domain, type, protocol);
|
||||
|
||||
u32 fd = next_fd++;
|
||||
const auto [fd, bsd_errno] = SocketImpl(static_cast<Domain>(domain), static_cast<Type>(type),
|
||||
static_cast<Protocol>(protocol));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(fd);
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
rb.Push<s32>(fd);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::Select(Kernel::HLERequestContext& ctx) {
|
||||
@@ -52,67 +171,664 @@ void BSD::Select(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Poll(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 nfds = rp.Pop<s32>();
|
||||
const s32 timeout = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
|
||||
|
||||
ExecuteWork(ctx, "BSD:Poll", timeout != 0,
|
||||
PollWork{
|
||||
.nfds = nfds,
|
||||
.timeout = timeout,
|
||||
.read_buffer = ctx.ReadBuffer(),
|
||||
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::Accept(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={}", fd);
|
||||
|
||||
ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd),
|
||||
AcceptWork{
|
||||
.fd = fd,
|
||||
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::Bind(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
BuildErrnoResponse(ctx, BindImpl(fd, ctx.ReadBuffer()));
|
||||
}
|
||||
|
||||
void BSD::Connect(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
|
||||
|
||||
ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd),
|
||||
ConnectWork{
|
||||
.fd = fd,
|
||||
.addr = ctx.ReadBuffer(),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::GetPeerName(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={}", fd);
|
||||
|
||||
std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
|
||||
const Errno bsd_errno = GetPeerNameImpl(fd, write_buffer);
|
||||
|
||||
ctx.WriteBuffer(write_buffer);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
|
||||
rb.PushEnum(bsd_errno);
|
||||
rb.Push<u32>(static_cast<u32>(write_buffer.size()));
|
||||
}
|
||||
|
||||
void BSD::GetSockName(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={}", fd);
|
||||
|
||||
std::vector<u8> write_buffer(ctx.GetWriteBufferSize());
|
||||
const Errno bsd_errno = GetSockNameImpl(fd, write_buffer);
|
||||
|
||||
ctx.WriteBuffer(write_buffer);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 5};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<s32>(bsd_errno != Errno::SUCCESS ? -1 : 0);
|
||||
rb.PushEnum(bsd_errno);
|
||||
rb.Push<u32>(static_cast<u32>(write_buffer.size()));
|
||||
}
|
||||
|
||||
void BSD::Listen(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const s32 backlog = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} backlog={}", fd, backlog);
|
||||
|
||||
BuildErrnoResponse(ctx, ListenImpl(fd, backlog));
|
||||
}
|
||||
|
||||
void BSD::Fcntl(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const s32 cmd = rp.Pop<s32>();
|
||||
const s32 arg = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
|
||||
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
rb.Push<s32>(ret);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 level = rp.Pop<u32>();
|
||||
const OptName optname = static_cast<OptName>(rp.Pop<u32>());
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
const std::vector<u8> buffer = ctx.ReadBuffer();
|
||||
const u8* optval = buffer.empty() ? nullptr : buffer.data();
|
||||
size_t optlen = buffer.size();
|
||||
|
||||
std::array<u64, 2> values;
|
||||
if ((optname == OptName::SNDTIMEO || optname == OptName::RCVTIMEO) && buffer.size() == 8) {
|
||||
std::memcpy(values.data(), buffer.data(), sizeof(values));
|
||||
optlen = sizeof(values);
|
||||
optval = reinterpret_cast<const u8*>(values.data());
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} level={} optname=0x{:x} optlen={}", fd, level,
|
||||
static_cast<u32>(optname), optlen);
|
||||
|
||||
BuildErrnoResponse(ctx, SetSockOptImpl(fd, level, optname, optlen, optval));
|
||||
}
|
||||
|
||||
void BSD::Shutdown(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const s32 how = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} how={}", fd, how);
|
||||
|
||||
BuildErrnoResponse(ctx, ShutdownImpl(fd, how));
|
||||
}
|
||||
|
||||
void BSD::Recv(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
|
||||
|
||||
ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd),
|
||||
RecvWork{
|
||||
.fd = fd,
|
||||
.flags = flags,
|
||||
.message = std::vector<u8>(ctx.GetWriteBufferSize()),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
|
||||
ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
|
||||
|
||||
ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd),
|
||||
RecvFromWork{
|
||||
.fd = fd,
|
||||
.flags = flags,
|
||||
.message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
|
||||
.addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::Send(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
|
||||
|
||||
ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd),
|
||||
SendWork{
|
||||
.fd = fd,
|
||||
.flags = flags,
|
||||
.message = ctx.ReadBuffer(),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
|
||||
ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd),
|
||||
SendToWork{
|
||||
.fd = fd,
|
||||
.flags = flags,
|
||||
.message = ctx.ReadBuffer(0),
|
||||
.addr = ctx.ReadBuffer(1),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::Write(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
|
||||
|
||||
ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd),
|
||||
SendWork{
|
||||
.fd = fd,
|
||||
.flags = 0,
|
||||
.message = ctx.ReadBuffer(),
|
||||
});
|
||||
}
|
||||
|
||||
void BSD::Close(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 fd = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={}", fd);
|
||||
|
||||
BuildErrnoResponse(ctx, CloseImpl(fd));
|
||||
}
|
||||
|
||||
template <typename Work>
|
||||
void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
|
||||
bool is_blocking, Work work) {
|
||||
if (!is_blocking) {
|
||||
work.Execute(this);
|
||||
work.Response(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal a dummy response to make IPC validation happy
|
||||
// This will be overwritten by the SleepClientThread callback
|
||||
work.Response(ctx);
|
||||
|
||||
auto worker = worker_pool.CaptureWorker();
|
||||
|
||||
ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(),
|
||||
worker->Callback<Work>(), worker->KernelEvent());
|
||||
|
||||
worker->SendWork(std::move(work));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
|
||||
if (type == Type::SEQPACKET) {
|
||||
UNIMPLEMENTED_MSG("SOCK_SEQPACKET errno management");
|
||||
} else if (type == Type::RAW && (domain != Domain::INET || protocol != Protocol::ICMP)) {
|
||||
UNIMPLEMENTED_MSG("SOCK_RAW errno management");
|
||||
}
|
||||
|
||||
[[maybe_unused]] const bool unk_flag = (static_cast<u32>(type) & 0x20000000) != 0;
|
||||
UNIMPLEMENTED_IF_MSG(unk_flag, "Unknown flag in type");
|
||||
type = static_cast<Type>(static_cast<u32>(type) & ~0x20000000);
|
||||
|
||||
const s32 fd = FindFreeFileDescriptorHandle();
|
||||
if (fd < 0) {
|
||||
LOG_ERROR(Service, "No more file descriptors available");
|
||||
return {-1, Errno::MFILE};
|
||||
}
|
||||
|
||||
FileDescriptor& descriptor = file_descriptors[fd].emplace();
|
||||
// ENONMEM might be thrown here
|
||||
|
||||
LOG_INFO(Service, "New socket fd={}", fd);
|
||||
|
||||
descriptor.socket = std::make_unique<Network::Socket>();
|
||||
descriptor.socket->Initialize(Translate(domain), Translate(type), Translate(type, protocol));
|
||||
descriptor.is_connection_based = IsConnectionBased(type);
|
||||
|
||||
return {fd, Errno::SUCCESS};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
|
||||
s32 nfds, s32 timeout) {
|
||||
if (write_buffer.size() < nfds * sizeof(PollFD)) {
|
||||
return {-1, Errno::INVAL};
|
||||
}
|
||||
|
||||
if (nfds == 0) {
|
||||
// When no entries are provided, -1 is returned with errno zero
|
||||
return {-1, Errno::SUCCESS};
|
||||
}
|
||||
|
||||
const size_t length = std::min(read_buffer.size(), write_buffer.size());
|
||||
std::vector<PollFD> fds(nfds);
|
||||
std::memcpy(fds.data(), read_buffer.data(), length);
|
||||
|
||||
if (timeout >= 0) {
|
||||
const s64 seconds = timeout / 1000;
|
||||
const u64 nanoseconds = 1'000'000 * (static_cast<u64>(timeout) % 1000);
|
||||
|
||||
if (seconds < 0) {
|
||||
return {-1, Errno::INVAL};
|
||||
}
|
||||
if (nanoseconds > 999'999'999) {
|
||||
return {-1, Errno::INVAL};
|
||||
}
|
||||
} else if (timeout != -1) {
|
||||
return {-1, Errno::INVAL};
|
||||
}
|
||||
|
||||
for (PollFD& pollfd : fds) {
|
||||
ASSERT(pollfd.revents == 0);
|
||||
|
||||
if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
|
||||
LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
|
||||
pollfd.revents = 0;
|
||||
return {0, Errno::SUCCESS};
|
||||
}
|
||||
|
||||
std::optional<FileDescriptor>& descriptor = file_descriptors[pollfd.fd];
|
||||
if (!descriptor) {
|
||||
LOG_ERROR(Service, "File descriptor handle={} is not allocated", pollfd.fd);
|
||||
pollfd.revents = POLL_NVAL;
|
||||
return {0, Errno::SUCCESS};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Network::PollFD> host_pollfds(fds.size());
|
||||
std::transform(fds.begin(), fds.end(), host_pollfds.begin(), [this](PollFD pollfd) {
|
||||
Network::PollFD result;
|
||||
result.socket = file_descriptors[pollfd.fd]->socket.get();
|
||||
result.events = TranslatePollEventsToHost(pollfd.events);
|
||||
result.revents = 0;
|
||||
return result;
|
||||
});
|
||||
|
||||
const auto result = Network::Poll(host_pollfds, timeout);
|
||||
|
||||
const size_t num = host_pollfds.size();
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
fds[i].revents = TranslatePollEventsToGuest(host_pollfds[i].revents);
|
||||
}
|
||||
std::memcpy(write_buffer.data(), fds.data(), length);
|
||||
|
||||
return Translate(result);
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::AcceptImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
const s32 new_fd = FindFreeFileDescriptorHandle();
|
||||
if (new_fd < 0) {
|
||||
LOG_ERROR(Service, "No more file descriptors available");
|
||||
return {-1, Errno::MFILE};
|
||||
}
|
||||
|
||||
FileDescriptor& descriptor = *file_descriptors[fd];
|
||||
auto [result, bsd_errno] = descriptor.socket->Accept();
|
||||
if (bsd_errno != Network::Errno::SUCCESS) {
|
||||
return {-1, Translate(bsd_errno)};
|
||||
}
|
||||
|
||||
FileDescriptor& new_descriptor = file_descriptors[new_fd].emplace();
|
||||
new_descriptor.socket = std::move(result.socket);
|
||||
new_descriptor.is_connection_based = descriptor.is_connection_based;
|
||||
|
||||
ASSERT(write_buffer.size() == sizeof(SockAddrIn));
|
||||
const SockAddrIn guest_addr_in = Translate(result.sockaddr_in);
|
||||
std::memcpy(write_buffer.data(), &guest_addr_in, sizeof(guest_addr_in));
|
||||
|
||||
return {new_fd, Errno::SUCCESS};
|
||||
}
|
||||
|
||||
Errno BSD::BindImpl(s32 fd, const std::vector<u8>& addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
ASSERT(addr.size() == sizeof(SockAddrIn));
|
||||
SockAddrIn addr_in;
|
||||
std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
|
||||
|
||||
return Translate(file_descriptors[fd]->socket->Bind(Translate(addr_in)));
|
||||
}
|
||||
|
||||
Errno BSD::ConnectImpl(s32 fd, const std::vector<u8>& addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_IF(addr.size() != sizeof(SockAddrIn));
|
||||
SockAddrIn addr_in;
|
||||
std::memcpy(&addr_in, addr.data(), sizeof(addr_in));
|
||||
|
||||
return Translate(file_descriptors[fd]->socket->Connect(Translate(addr_in)));
|
||||
}
|
||||
|
||||
Errno BSD::GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetPeerName();
|
||||
if (bsd_errno != Network::Errno::SUCCESS) {
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
ASSERT(write_buffer.size() == sizeof(guest_addrin));
|
||||
std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
|
||||
Errno BSD::GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
const auto [addr_in, bsd_errno] = file_descriptors[fd]->socket->GetSockName();
|
||||
if (bsd_errno != Network::Errno::SUCCESS) {
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
const SockAddrIn guest_addrin = Translate(addr_in);
|
||||
|
||||
ASSERT(write_buffer.size() == sizeof(guest_addrin));
|
||||
std::memcpy(write_buffer.data(), &guest_addrin, sizeof(guest_addrin));
|
||||
return Translate(bsd_errno);
|
||||
}
|
||||
|
||||
Errno BSD::ListenImpl(s32 fd, s32 backlog) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Listen(backlog));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
FileDescriptor& descriptor = *file_descriptors[fd];
|
||||
|
||||
switch (cmd) {
|
||||
case FcntlCmd::GETFL:
|
||||
ASSERT(arg == 0);
|
||||
return {descriptor.flags, Errno::SUCCESS};
|
||||
case FcntlCmd::SETFL: {
|
||||
const bool enable = (arg & FLAG_O_NONBLOCK) != 0;
|
||||
const Errno bsd_errno = Translate(descriptor.socket->SetNonBlock(enable));
|
||||
if (bsd_errno != Errno::SUCCESS) {
|
||||
return {-1, bsd_errno};
|
||||
}
|
||||
descriptor.flags = arg;
|
||||
return {0, Errno::SUCCESS};
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented cmd={}", static_cast<int>(cmd));
|
||||
return {-1, Errno::SUCCESS};
|
||||
}
|
||||
}
|
||||
|
||||
Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval) {
|
||||
UNIMPLEMENTED_IF(level != 0xffff); // SOL_SOCKET
|
||||
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
Network::Socket* const socket = file_descriptors[fd]->socket.get();
|
||||
|
||||
if (optname == OptName::LINGER) {
|
||||
ASSERT(optlen == sizeof(Linger));
|
||||
Linger linger;
|
||||
std::memcpy(&linger, optval, sizeof(linger));
|
||||
ASSERT(linger.onoff == 0 || linger.onoff == 1);
|
||||
|
||||
return Translate(socket->SetLinger(linger.onoff != 0, linger.linger));
|
||||
}
|
||||
|
||||
ASSERT(optlen == sizeof(u32));
|
||||
u32 value;
|
||||
std::memcpy(&value, optval, sizeof(value));
|
||||
|
||||
switch (optname) {
|
||||
case OptName::REUSEADDR:
|
||||
ASSERT(value == 0 || value == 1);
|
||||
return Translate(socket->SetReuseAddr(value != 0));
|
||||
case OptName::BROADCAST:
|
||||
ASSERT(value == 0 || value == 1);
|
||||
return Translate(socket->SetBroadcast(value != 0));
|
||||
case OptName::SNDBUF:
|
||||
return Translate(socket->SetSndBuf(value));
|
||||
case OptName::RCVBUF:
|
||||
return Translate(socket->SetRcvBuf(value));
|
||||
case OptName::SNDTIMEO:
|
||||
return Translate(socket->SetSndTimeo(value));
|
||||
case OptName::RCVTIMEO:
|
||||
return Translate(socket->SetRcvTimeo(value));
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented optname={}", static_cast<int>(optname));
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
Errno BSD::ShutdownImpl(s32 fd, s32 how) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
const Network::ShutdownHow host_how = Translate(static_cast<ShutdownHow>(how));
|
||||
return Translate(file_descriptors[fd]->socket->Shutdown(host_how));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::RecvImpl(s32 fd, u32 flags, std::vector<u8>& message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Recv(flags, message));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
|
||||
std::vector<u8>& addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
FileDescriptor& descriptor = *file_descriptors[fd];
|
||||
|
||||
Network::SockAddrIn addr_in{};
|
||||
Network::SockAddrIn* p_addr_in = nullptr;
|
||||
if (descriptor.is_connection_based) {
|
||||
// Connection based file descriptors (e.g. TCP) zero addr
|
||||
addr.clear();
|
||||
} else {
|
||||
p_addr_in = &addr_in;
|
||||
}
|
||||
|
||||
// Apply flags
|
||||
if ((flags & FLAG_MSG_DONTWAIT) != 0) {
|
||||
flags &= ~FLAG_MSG_DONTWAIT;
|
||||
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
|
||||
descriptor.socket->SetNonBlock(true);
|
||||
}
|
||||
}
|
||||
|
||||
const auto [ret, bsd_errno] = Translate(descriptor.socket->RecvFrom(flags, message, p_addr_in));
|
||||
|
||||
// Restore original state
|
||||
if ((descriptor.flags & FLAG_O_NONBLOCK) == 0) {
|
||||
descriptor.socket->SetNonBlock(false);
|
||||
}
|
||||
|
||||
if (p_addr_in) {
|
||||
if (ret < 0) {
|
||||
addr.clear();
|
||||
} else {
|
||||
ASSERT(addr.size() == sizeof(SockAddrIn));
|
||||
const SockAddrIn result = Translate(addr_in);
|
||||
std::memcpy(addr.data(), &result, sizeof(result));
|
||||
}
|
||||
}
|
||||
|
||||
return {ret, bsd_errno};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, const std::vector<u8>& message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
|
||||
const std::vector<u8>& addr) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
Network::SockAddrIn addr_in;
|
||||
Network::SockAddrIn* p_addr_in = nullptr;
|
||||
if (!addr.empty()) {
|
||||
ASSERT(addr.size() == sizeof(SockAddrIn));
|
||||
SockAddrIn guest_addr_in;
|
||||
std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
|
||||
addr_in = Translate(guest_addr_in);
|
||||
p_addr_in = &addr_in;
|
||||
}
|
||||
|
||||
return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
|
||||
}
|
||||
|
||||
Errno BSD::CloseImpl(s32 fd) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return Errno::BADF;
|
||||
}
|
||||
|
||||
const Errno bsd_errno = Translate(file_descriptors[fd]->socket->Close());
|
||||
if (bsd_errno != Errno::SUCCESS) {
|
||||
return bsd_errno;
|
||||
}
|
||||
|
||||
LOG_INFO(Service, "Close socket fd={}", fd);
|
||||
|
||||
file_descriptors[fd].reset();
|
||||
return bsd_errno;
|
||||
}
|
||||
|
||||
s32 BSD::FindFreeFileDescriptorHandle() noexcept {
|
||||
for (s32 fd = 0; fd < static_cast<s32>(file_descriptors.size()); ++fd) {
|
||||
if (!file_descriptors[fd]) {
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
|
||||
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
|
||||
LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
|
||||
return false;
|
||||
}
|
||||
if (!file_descriptors[fd]) {
|
||||
LOG_ERROR(Service, "File descriptor handle={} is not allocated", fd);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BSD::IsBlockingSocket(s32 fd) const noexcept {
|
||||
// Inform invalid sockets as non-blocking
|
||||
// This way we avoid using a worker thread as it will fail without blocking host
|
||||
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!file_descriptors[fd]) {
|
||||
return false;
|
||||
}
|
||||
return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0;
|
||||
}
|
||||
|
||||
void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
rb.Push<s32>(bsd_errno == Errno::SUCCESS ? 0 : -1);
|
||||
rb.PushEnum(bsd_errno);
|
||||
}
|
||||
|
||||
BSD::BSD(const char* name) : ServiceFramework(name) {
|
||||
BSD::BSD(Core::System& system, const char* name)
|
||||
: ServiceFramework(name), worker_pool{system, this} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &BSD::RegisterClient, "RegisterClient"},
|
||||
@@ -121,25 +837,25 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
|
||||
{3, nullptr, "SocketExempt"},
|
||||
{4, nullptr, "Open"},
|
||||
{5, &BSD::Select, "Select"},
|
||||
{6, nullptr, "Poll"},
|
||||
{6, &BSD::Poll, "Poll"},
|
||||
{7, nullptr, "Sysctl"},
|
||||
{8, nullptr, "Recv"},
|
||||
{9, nullptr, "RecvFrom"},
|
||||
{10, nullptr, "Send"},
|
||||
{8, &BSD::Recv, "Recv"},
|
||||
{9, &BSD::RecvFrom, "RecvFrom"},
|
||||
{10, &BSD::Send, "Send"},
|
||||
{11, &BSD::SendTo, "SendTo"},
|
||||
{12, nullptr, "Accept"},
|
||||
{12, &BSD::Accept, "Accept"},
|
||||
{13, &BSD::Bind, "Bind"},
|
||||
{14, &BSD::Connect, "Connect"},
|
||||
{15, nullptr, "GetPeerName"},
|
||||
{16, nullptr, "GetSockName"},
|
||||
{15, &BSD::GetPeerName, "GetPeerName"},
|
||||
{16, &BSD::GetSockName, "GetSockName"},
|
||||
{17, nullptr, "GetSockOpt"},
|
||||
{18, &BSD::Listen, "Listen"},
|
||||
{19, nullptr, "Ioctl"},
|
||||
{20, nullptr, "Fcntl"},
|
||||
{20, &BSD::Fcntl, "Fcntl"},
|
||||
{21, &BSD::SetSockOpt, "SetSockOpt"},
|
||||
{22, nullptr, "Shutdown"},
|
||||
{22, &BSD::Shutdown, "Shutdown"},
|
||||
{23, nullptr, "ShutdownAllSockets"},
|
||||
{24, nullptr, "Write"},
|
||||
{24, &BSD::Write, "Write"},
|
||||
{25, nullptr, "Read"},
|
||||
{26, &BSD::Close, "Close"},
|
||||
{27, nullptr, "DuplicateSocket"},
|
||||
|
||||
@@ -4,30 +4,174 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sockets/blocking_worker.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Network {
|
||||
class Socket;
|
||||
}
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
class BSD final : public ServiceFramework<BSD> {
|
||||
public:
|
||||
explicit BSD(const char* name);
|
||||
explicit BSD(Core::System& system, const char* name);
|
||||
~BSD() override;
|
||||
|
||||
private:
|
||||
/// Maximum number of file descriptors
|
||||
static constexpr size_t MAX_FD = 128;
|
||||
|
||||
struct FileDescriptor {
|
||||
std::unique_ptr<Network::Socket> socket;
|
||||
s32 flags = 0;
|
||||
bool is_connection_based = false;
|
||||
};
|
||||
|
||||
struct PollWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 nfds;
|
||||
s32 timeout;
|
||||
std::vector<u8> read_buffer;
|
||||
std::vector<u8> write_buffer;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct AcceptWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
std::vector<u8> write_buffer;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct ConnectWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
std::vector<u8> addr;
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct RecvWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
u32 flags;
|
||||
std::vector<u8> message;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct RecvFromWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
u32 flags;
|
||||
std::vector<u8> message;
|
||||
std::vector<u8> addr;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct SendWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
u32 flags;
|
||||
std::vector<u8> message;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
struct SendToWork {
|
||||
void Execute(BSD* bsd);
|
||||
void Response(Kernel::HLERequestContext& ctx);
|
||||
|
||||
s32 fd;
|
||||
u32 flags;
|
||||
std::vector<u8> message;
|
||||
std::vector<u8> addr;
|
||||
s32 ret{};
|
||||
Errno bsd_errno{};
|
||||
};
|
||||
|
||||
void RegisterClient(Kernel::HLERequestContext& ctx);
|
||||
void StartMonitoring(Kernel::HLERequestContext& ctx);
|
||||
void Socket(Kernel::HLERequestContext& ctx);
|
||||
void Select(Kernel::HLERequestContext& ctx);
|
||||
void Poll(Kernel::HLERequestContext& ctx);
|
||||
void Accept(Kernel::HLERequestContext& ctx);
|
||||
void Bind(Kernel::HLERequestContext& ctx);
|
||||
void Connect(Kernel::HLERequestContext& ctx);
|
||||
void GetPeerName(Kernel::HLERequestContext& ctx);
|
||||
void GetSockName(Kernel::HLERequestContext& ctx);
|
||||
void Listen(Kernel::HLERequestContext& ctx);
|
||||
void Fcntl(Kernel::HLERequestContext& ctx);
|
||||
void SetSockOpt(Kernel::HLERequestContext& ctx);
|
||||
void Shutdown(Kernel::HLERequestContext& ctx);
|
||||
void Recv(Kernel::HLERequestContext& ctx);
|
||||
void RecvFrom(Kernel::HLERequestContext& ctx);
|
||||
void Send(Kernel::HLERequestContext& ctx);
|
||||
void SendTo(Kernel::HLERequestContext& ctx);
|
||||
void Write(Kernel::HLERequestContext& ctx);
|
||||
void Close(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/// Id to use for the next open file descriptor.
|
||||
u32 next_fd = 1;
|
||||
template <typename Work>
|
||||
void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason,
|
||||
bool is_blocking, Work work);
|
||||
|
||||
std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
|
||||
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
|
||||
s32 nfds, s32 timeout);
|
||||
std::pair<s32, Errno> AcceptImpl(s32 fd, std::vector<u8>& write_buffer);
|
||||
Errno BindImpl(s32 fd, const std::vector<u8>& addr);
|
||||
Errno ConnectImpl(s32 fd, const std::vector<u8>& addr);
|
||||
Errno GetPeerNameImpl(s32 fd, std::vector<u8>& write_buffer);
|
||||
Errno GetSockNameImpl(s32 fd, std::vector<u8>& write_buffer);
|
||||
Errno ListenImpl(s32 fd, s32 backlog);
|
||||
std::pair<s32, Errno> FcntlImpl(s32 fd, FcntlCmd cmd, s32 arg);
|
||||
Errno SetSockOptImpl(s32 fd, u32 level, OptName optname, size_t optlen, const void* optval);
|
||||
Errno ShutdownImpl(s32 fd, s32 how);
|
||||
std::pair<s32, Errno> RecvImpl(s32 fd, u32 flags, std::vector<u8>& message);
|
||||
std::pair<s32, Errno> RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& message,
|
||||
std::vector<u8>& addr);
|
||||
std::pair<s32, Errno> SendImpl(s32 fd, u32 flags, const std::vector<u8>& message);
|
||||
std::pair<s32, Errno> SendToImpl(s32 fd, u32 flags, const std::vector<u8>& message,
|
||||
const std::vector<u8>& addr);
|
||||
Errno CloseImpl(s32 fd);
|
||||
|
||||
s32 FindFreeFileDescriptorHandle() noexcept;
|
||||
bool IsFileDescriptorValid(s32 fd) const noexcept;
|
||||
bool IsBlockingSocket(s32 fd) const noexcept;
|
||||
|
||||
void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
|
||||
|
||||
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
|
||||
|
||||
BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
|
||||
SendToWork>
|
||||
worker_pool;
|
||||
};
|
||||
|
||||
class BSDCFG final : public ServiceFramework<BSDCFG> {
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<BSD>("bsd:s")->InstallAsService(service_manager);
|
||||
std::make_shared<BSD>("bsd:u")->InstallAsService(service_manager);
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
|
||||
std::make_shared<BSD>(system, "bsd:s")->InstallAsService(service_manager);
|
||||
std::make_shared<BSD>(system, "bsd:u")->InstallAsService(service_manager);
|
||||
std::make_shared<BSDCFG>()->InstallAsService(service_manager);
|
||||
|
||||
std::make_shared<ETHC_C>()->InstallAsService(service_manager);
|
||||
|
||||
@@ -4,11 +4,94 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
enum class Errno : u32 {
|
||||
SUCCESS = 0,
|
||||
BADF = 9,
|
||||
AGAIN = 11,
|
||||
INVAL = 22,
|
||||
MFILE = 24,
|
||||
NOTCONN = 107,
|
||||
};
|
||||
|
||||
enum class Domain : u32 {
|
||||
INET = 2,
|
||||
};
|
||||
|
||||
enum class Type : u32 {
|
||||
STREAM = 1,
|
||||
DGRAM = 2,
|
||||
RAW = 3,
|
||||
SEQPACKET = 5,
|
||||
};
|
||||
|
||||
enum class Protocol : u32 {
|
||||
UNSPECIFIED = 0,
|
||||
ICMP = 1,
|
||||
TCP = 6,
|
||||
UDP = 17,
|
||||
};
|
||||
|
||||
enum class OptName : u32 {
|
||||
REUSEADDR = 0x4,
|
||||
BROADCAST = 0x20,
|
||||
LINGER = 0x80,
|
||||
SNDBUF = 0x1001,
|
||||
RCVBUF = 0x1002,
|
||||
SNDTIMEO = 0x1005,
|
||||
RCVTIMEO = 0x1006,
|
||||
};
|
||||
|
||||
enum class ShutdownHow : s32 {
|
||||
RD = 0,
|
||||
WR = 1,
|
||||
RDWR = 2,
|
||||
};
|
||||
|
||||
enum class FcntlCmd : s32 {
|
||||
GETFL = 3,
|
||||
SETFL = 4,
|
||||
};
|
||||
|
||||
struct SockAddrIn {
|
||||
u8 len;
|
||||
u8 family;
|
||||
u16 portno;
|
||||
std::array<u8, 4> ip;
|
||||
std::array<u8, 8> zeroes;
|
||||
};
|
||||
|
||||
struct PollFD {
|
||||
s32 fd;
|
||||
u16 events;
|
||||
u16 revents;
|
||||
};
|
||||
|
||||
struct Linger {
|
||||
u32 onoff;
|
||||
u32 linger;
|
||||
};
|
||||
|
||||
constexpr u16 POLL_IN = 0x01;
|
||||
constexpr u16 POLL_PRI = 0x02;
|
||||
constexpr u16 POLL_OUT = 0x04;
|
||||
constexpr u16 POLL_ERR = 0x08;
|
||||
constexpr u16 POLL_HUP = 0x10;
|
||||
constexpr u16 POLL_NVAL = 0x20;
|
||||
|
||||
constexpr u32 FLAG_MSG_DONTWAIT = 0x80;
|
||||
|
||||
constexpr u32 FLAG_O_NONBLOCK = 0x800;
|
||||
|
||||
/// Registers all Sockets services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/hle/service/sockets/sockets_translate.h"
|
||||
#include "core/network/network.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
Errno Translate(Network::Errno value) {
|
||||
switch (value) {
|
||||
case Network::Errno::SUCCESS:
|
||||
return Errno::SUCCESS;
|
||||
case Network::Errno::BADF:
|
||||
return Errno::BADF;
|
||||
case Network::Errno::AGAIN:
|
||||
return Errno::AGAIN;
|
||||
case Network::Errno::INVAL:
|
||||
return Errno::INVAL;
|
||||
case Network::Errno::MFILE:
|
||||
return Errno::MFILE;
|
||||
case Network::Errno::NOTCONN:
|
||||
return Errno::NOTCONN;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", static_cast<int>(value));
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value) {
|
||||
return {value.first, Translate(value.second)};
|
||||
}
|
||||
|
||||
Network::Domain Translate(Domain domain) {
|
||||
switch (domain) {
|
||||
case Domain::INET:
|
||||
return Network::Domain::INET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Domain Translate(Network::Domain domain) {
|
||||
switch (domain) {
|
||||
case Network::Domain::INET:
|
||||
return Domain::INET;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented domain={}", static_cast<int>(domain));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Network::Type Translate(Type type) {
|
||||
switch (type) {
|
||||
case Type::STREAM:
|
||||
return Network::Type::STREAM;
|
||||
case Type::DGRAM:
|
||||
return Network::Type::DGRAM;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented type={}", static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
|
||||
Network::Protocol Translate(Type type, Protocol protocol) {
|
||||
switch (protocol) {
|
||||
case Protocol::UNSPECIFIED:
|
||||
LOG_WARNING(Service, "Unspecified protocol, assuming protocol from type");
|
||||
switch (type) {
|
||||
case Type::DGRAM:
|
||||
return Network::Protocol::UDP;
|
||||
case Type::STREAM:
|
||||
return Network::Protocol::TCP;
|
||||
default:
|
||||
return Network::Protocol::TCP;
|
||||
}
|
||||
case Protocol::TCP:
|
||||
return Network::Protocol::TCP;
|
||||
case Protocol::UDP:
|
||||
return Network::Protocol::UDP;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented protocol={}", static_cast<int>(protocol));
|
||||
return Network::Protocol::TCP;
|
||||
}
|
||||
}
|
||||
|
||||
u16 TranslatePollEventsToHost(u16 flags) {
|
||||
u16 result = 0;
|
||||
const auto translate = [&result, &flags](u16 from, u16 to) {
|
||||
if ((flags & from) != 0) {
|
||||
flags &= ~from;
|
||||
result |= to;
|
||||
}
|
||||
};
|
||||
translate(POLL_IN, Network::POLL_IN);
|
||||
translate(POLL_PRI, Network::POLL_PRI);
|
||||
translate(POLL_OUT, Network::POLL_OUT);
|
||||
translate(POLL_ERR, Network::POLL_ERR);
|
||||
translate(POLL_HUP, Network::POLL_HUP);
|
||||
translate(POLL_NVAL, Network::POLL_NVAL);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
u16 TranslatePollEventsToGuest(u16 flags) {
|
||||
u16 result = 0;
|
||||
const auto translate = [&result, &flags](u16 from, u16 to) {
|
||||
if ((flags & from) != 0) {
|
||||
flags &= ~from;
|
||||
result |= to;
|
||||
}
|
||||
};
|
||||
|
||||
translate(Network::POLL_IN, POLL_IN);
|
||||
translate(Network::POLL_PRI, POLL_PRI);
|
||||
translate(Network::POLL_OUT, POLL_OUT);
|
||||
translate(Network::POLL_ERR, POLL_ERR);
|
||||
translate(Network::POLL_HUP, POLL_HUP);
|
||||
translate(Network::POLL_NVAL, POLL_NVAL);
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(flags != 0, "Unimplemented flags={}", flags);
|
||||
return result;
|
||||
}
|
||||
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value));
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
.ip = value.ip,
|
||||
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
|
||||
};
|
||||
}
|
||||
|
||||
SockAddrIn Translate(Network::SockAddrIn value) {
|
||||
return {
|
||||
.len = sizeof(SockAddrIn),
|
||||
.family = static_cast<u8>(Translate(value.family)),
|
||||
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
|
||||
.ip = value.ip,
|
||||
.zeroes = {},
|
||||
};
|
||||
}
|
||||
|
||||
Network::ShutdownHow Translate(ShutdownHow how) {
|
||||
switch (how) {
|
||||
case ShutdownHow::RD:
|
||||
return Network::ShutdownHow::RD;
|
||||
case ShutdownHow::WR:
|
||||
return Network::ShutdownHow::WR;
|
||||
case ShutdownHow::RDWR:
|
||||
return Network::ShutdownHow::RDWR;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented how={}", static_cast<int>(how));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::Sockets
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/sockets/sockets.h"
|
||||
#include "core/network/network.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
/// Translate abstract errno to guest errno
|
||||
Errno Translate(Network::Errno value);
|
||||
|
||||
/// Translate abstract return value errno pair to guest return value errno pair
|
||||
std::pair<s32, Errno> Translate(std::pair<s32, Network::Errno> value);
|
||||
|
||||
/// Translate guest domain to abstract domain
|
||||
Network::Domain Translate(Domain domain);
|
||||
|
||||
/// Translate abstract domain to guest domain
|
||||
Domain Translate(Network::Domain domain);
|
||||
|
||||
/// Translate guest type to abstract type
|
||||
Network::Type Translate(Type type);
|
||||
|
||||
/// Translate guest protocol to abstract protocol
|
||||
Network::Protocol Translate(Type type, Protocol protocol);
|
||||
|
||||
/// Translate abstract poll event flags to guest poll event flags
|
||||
u16 TranslatePollEventsToHost(u16 flags);
|
||||
|
||||
/// Translate guest poll event flags to abstract poll event flags
|
||||
u16 TranslatePollEventsToGuest(u16 flags);
|
||||
|
||||
/// Translate guest socket address structure to abstract socket address structure
|
||||
Network::SockAddrIn Translate(SockAddrIn value);
|
||||
|
||||
/// Translate abstract socket address structure to guest socket address structure
|
||||
SockAddrIn Translate(Network::SockAddrIn value);
|
||||
|
||||
/// Translate guest shutdown mode to abstract shutdown mode
|
||||
Network::ShutdownHow Translate(ShutdownHow how);
|
||||
|
||||
} // namespace Service::Sockets
|
||||
@@ -283,7 +283,7 @@ void Adapter::Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Adapter::DeviceConnected(std::size_t port) {
|
||||
bool Adapter::DeviceConnected(std::size_t port) const {
|
||||
return adapter_controllers_status[port] != ControllerTypes::None;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
void EndConfiguration();
|
||||
|
||||
/// Returns true if there is a device connected to port
|
||||
bool DeviceConnected(std::size_t port);
|
||||
bool DeviceConnected(std::size_t port) const;
|
||||
|
||||
std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
|
||||
const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace InputCommon {
|
||||
|
||||
class GCButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter)
|
||||
explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter)
|
||||
: port(port_), button(button_), gcadapter(adapter) {}
|
||||
|
||||
~GCButton() override;
|
||||
@@ -30,13 +30,13 @@ public:
|
||||
private:
|
||||
const int port;
|
||||
const int button;
|
||||
GCAdapter::Adapter* gcadapter;
|
||||
const GCAdapter::Adapter* gcadapter;
|
||||
};
|
||||
|
||||
class GCAxisButton final : public Input::ButtonDevice {
|
||||
public:
|
||||
explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
|
||||
GCAdapter::Adapter* adapter)
|
||||
const GCAdapter::Adapter* adapter)
|
||||
: port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
|
||||
gcadapter(adapter),
|
||||
origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
|
||||
@@ -60,7 +60,7 @@ private:
|
||||
const int axis;
|
||||
float threshold;
|
||||
bool trigger_if_greater;
|
||||
GCAdapter::Adapter* gcadapter;
|
||||
const GCAdapter::Adapter* gcadapter;
|
||||
const float origin_value;
|
||||
};
|
||||
|
||||
@@ -149,8 +149,8 @@ void GCButtonFactory::EndConfiguration() {
|
||||
|
||||
class GCAnalog final : public Input::AnalogDevice {
|
||||
public:
|
||||
GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter,
|
||||
float range_)
|
||||
GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_,
|
||||
const GCAdapter::Adapter* adapter, float range_)
|
||||
: port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
|
||||
origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
|
||||
origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
|
||||
@@ -212,7 +212,7 @@ private:
|
||||
const int axis_x;
|
||||
const int axis_y;
|
||||
const float deadzone;
|
||||
GCAdapter::Adapter* gcadapter;
|
||||
const GCAdapter::Adapter* gcadapter;
|
||||
const float origin_value_x;
|
||||
const float origin_value_y;
|
||||
const float range;
|
||||
|
||||
@@ -51,46 +51,43 @@ public:
|
||||
bool is_written = false, bool use_fast_cbuf = false) {
|
||||
std::lock_guard lock{mutex};
|
||||
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr_opt) {
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
return GetEmptyBuffer(size);
|
||||
}
|
||||
const VAddr cpu_addr = *cpu_addr_opt;
|
||||
|
||||
// Cache management is a big overhead, so only cache entries with a given size.
|
||||
// TODO: Figure out which size is the best for given games.
|
||||
constexpr std::size_t max_stream_size = 0x800;
|
||||
if (use_fast_cbuf || size < max_stream_size) {
|
||||
if (!is_written && !IsRegionWritten(cpu_addr, cpu_addr + size - 1)) {
|
||||
const bool is_granular = memory_manager.IsGranularRange(gpu_addr, size);
|
||||
if (!is_written && !IsRegionWritten(*cpu_addr, *cpu_addr + size - 1)) {
|
||||
const bool is_granular = gpu_memory.IsGranularRange(gpu_addr, size);
|
||||
if (use_fast_cbuf) {
|
||||
u8* dest;
|
||||
if (is_granular) {
|
||||
dest = memory_manager.GetPointer(gpu_addr);
|
||||
dest = gpu_memory.GetPointer(gpu_addr);
|
||||
} else {
|
||||
staging_buffer.resize(size);
|
||||
dest = staging_buffer.data();
|
||||
memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
|
||||
}
|
||||
return ConstBufferUpload(dest, size);
|
||||
}
|
||||
if (is_granular) {
|
||||
u8* const host_ptr = memory_manager.GetPointer(gpu_addr);
|
||||
u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
|
||||
return StreamBufferUpload(size, alignment, [host_ptr, size](u8* dest) {
|
||||
std::memcpy(dest, host_ptr, size);
|
||||
});
|
||||
} else {
|
||||
return StreamBufferUpload(
|
||||
size, alignment, [&memory_manager, gpu_addr, size](u8* dest) {
|
||||
memory_manager.ReadBlockUnsafe(gpu_addr, dest, size);
|
||||
});
|
||||
return StreamBufferUpload(size, alignment, [this, gpu_addr, size](u8* dest) {
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr, dest, size);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Buffer* const block = GetBlock(cpu_addr, size);
|
||||
MapInterval* const map = MapAddress(block, gpu_addr, cpu_addr, size);
|
||||
Buffer* const block = GetBlock(*cpu_addr, size);
|
||||
MapInterval* const map = MapAddress(block, gpu_addr, *cpu_addr, size);
|
||||
if (!map) {
|
||||
return GetEmptyBuffer(size);
|
||||
}
|
||||
@@ -106,7 +103,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
return BufferInfo{block->Handle(), block->Offset(cpu_addr), block->Address()};
|
||||
return BufferInfo{block->Handle(), block->Offset(*cpu_addr), block->Address()};
|
||||
}
|
||||
|
||||
/// Uploads from a host memory. Returns the OpenGL buffer where it's located and its offset.
|
||||
@@ -262,9 +259,11 @@ public:
|
||||
virtual BufferInfo GetEmptyBuffer(std::size_t size) = 0;
|
||||
|
||||
protected:
|
||||
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
|
||||
std::unique_ptr<StreamBuffer> stream_buffer)
|
||||
: rasterizer{rasterizer}, system{system}, stream_buffer{std::move(stream_buffer)} {}
|
||||
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
||||
Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
|
||||
std::unique_ptr<StreamBuffer> stream_buffer_)
|
||||
: rasterizer{rasterizer_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_},
|
||||
stream_buffer{std::move(stream_buffer_)}, stream_buffer_handle{stream_buffer->Handle()} {}
|
||||
|
||||
~BufferCache() = default;
|
||||
|
||||
@@ -326,14 +325,13 @@ private:
|
||||
MapInterval* MapAddress(Buffer* block, GPUVAddr gpu_addr, VAddr cpu_addr, std::size_t size) {
|
||||
const VectorMapInterval overlaps = GetMapsInRange(cpu_addr, size);
|
||||
if (overlaps.empty()) {
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const VAddr cpu_addr_end = cpu_addr + size;
|
||||
if (memory_manager.IsGranularRange(gpu_addr, size)) {
|
||||
u8* host_ptr = memory_manager.GetPointer(gpu_addr);
|
||||
if (gpu_memory.IsGranularRange(gpu_addr, size)) {
|
||||
u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
|
||||
block->Upload(block->Offset(cpu_addr), size, host_ptr);
|
||||
} else {
|
||||
staging_buffer.resize(size);
|
||||
memory_manager.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
|
||||
gpu_memory.ReadBlockUnsafe(gpu_addr, staging_buffer.data(), size);
|
||||
block->Upload(block->Offset(cpu_addr), size, staging_buffer.data());
|
||||
}
|
||||
return Register(MapInterval(cpu_addr, cpu_addr_end, gpu_addr));
|
||||
@@ -392,7 +390,7 @@ private:
|
||||
continue;
|
||||
}
|
||||
staging_buffer.resize(size);
|
||||
system.Memory().ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
|
||||
cpu_memory.ReadBlockUnsafe(interval.lower(), staging_buffer.data(), size);
|
||||
block->Upload(block->Offset(interval.lower()), size, staging_buffer.data());
|
||||
}
|
||||
}
|
||||
@@ -431,7 +429,7 @@ private:
|
||||
const std::size_t size = map->end - map->start;
|
||||
staging_buffer.resize(size);
|
||||
block->Download(block->Offset(map->start), size, staging_buffer.data());
|
||||
system.Memory().WriteBlockUnsafe(map->start, staging_buffer.data(), size);
|
||||
cpu_memory.WriteBlockUnsafe(map->start, staging_buffer.data(), size);
|
||||
map->MarkAsModified(false, 0);
|
||||
}
|
||||
|
||||
@@ -567,7 +565,8 @@ private:
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Core::System& system;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
Core::Memory::Memory& cpu_memory;
|
||||
|
||||
std::unique_ptr<StreamBuffer> stream_buffer;
|
||||
BufferType stream_buffer_handle;
|
||||
|
||||
@@ -74,8 +74,6 @@ public:
|
||||
}
|
||||
|
||||
void WaitPendingFences() {
|
||||
auto& gpu{system.GPU()};
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
while (!fences.empty()) {
|
||||
TFence& current_fence = fences.front();
|
||||
if (ShouldWait()) {
|
||||
@@ -83,8 +81,8 @@ public:
|
||||
}
|
||||
PopAsyncFlushes();
|
||||
if (current_fence->IsSemaphore()) {
|
||||
memory_manager.template Write<u32>(current_fence->GetAddress(),
|
||||
current_fence->GetPayload());
|
||||
gpu_memory.template Write<u32>(current_fence->GetAddress(),
|
||||
current_fence->GetPayload());
|
||||
} else {
|
||||
gpu.IncrementSyncPoint(current_fence->GetPayload());
|
||||
}
|
||||
@@ -93,13 +91,13 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
FenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
TTextureCache& texture_cache, TTBufferCache& buffer_cache,
|
||||
TQueryCache& query_cache)
|
||||
: system{system}, rasterizer{rasterizer}, texture_cache{texture_cache},
|
||||
buffer_cache{buffer_cache}, query_cache{query_cache} {}
|
||||
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
|
||||
TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
|
||||
TQueryCache& query_cache_)
|
||||
: rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
|
||||
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
|
||||
|
||||
virtual ~FenceManager() {}
|
||||
virtual ~FenceManager() = default;
|
||||
|
||||
/// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
|
||||
/// true
|
||||
@@ -113,16 +111,15 @@ protected:
|
||||
/// Waits until a fence has been signalled by the host GPU.
|
||||
virtual void WaitFence(TFence& fence) = 0;
|
||||
|
||||
Core::System& system;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
TTextureCache& texture_cache;
|
||||
TTBufferCache& buffer_cache;
|
||||
TQueryCache& query_cache;
|
||||
|
||||
private:
|
||||
void TryReleasePendingFences() {
|
||||
auto& gpu{system.GPU()};
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
while (!fences.empty()) {
|
||||
TFence& current_fence = fences.front();
|
||||
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
|
||||
@@ -130,8 +127,8 @@ private:
|
||||
}
|
||||
PopAsyncFlushes();
|
||||
if (current_fence->IsSemaphore()) {
|
||||
memory_manager.template Write<u32>(current_fence->GetAddress(),
|
||||
current_fence->GetPayload());
|
||||
gpu_memory.template Write<u32>(current_fence->GetAddress(),
|
||||
current_fence->GetPayload());
|
||||
} else {
|
||||
gpu.IncrementSyncPoint(current_fence->GetPayload());
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ namespace Tegra {
|
||||
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
|
||||
|
||||
GPU::GPU(Core::System& system_, bool is_async_)
|
||||
: system{system_}, dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
|
||||
memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
|
||||
: system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(system)},
|
||||
dma_pusher{std::make_unique<Tegra::DmaPusher>(system, *this)},
|
||||
maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
|
||||
fermi_2d{std::make_unique<Engines::Fermi2D>()},
|
||||
kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
|
||||
|
||||
@@ -347,12 +347,11 @@ private:
|
||||
|
||||
protected:
|
||||
Core::System& system;
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
||||
|
||||
/// Mapping of command subchannels to their bound engine ids
|
||||
std::array<EngineID, 8> bound_engines = {};
|
||||
/// 3D engine
|
||||
|
||||
@@ -95,10 +95,12 @@ template <class QueryCache, class CachedQuery, class CounterStream, class HostCo
|
||||
class QueryPool>
|
||||
class QueryCacheBase {
|
||||
public:
|
||||
explicit QueryCacheBase(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
|
||||
: system{system}, rasterizer{rasterizer}, streams{{CounterStream{
|
||||
static_cast<QueryCache&>(*this),
|
||||
VideoCore::QueryType::SamplesPassed}}} {}
|
||||
explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
||||
Tegra::MemoryManager& gpu_memory_)
|
||||
: rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
|
||||
gpu_memory{gpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
|
||||
VideoCore::QueryType::SamplesPassed}}} {}
|
||||
|
||||
void InvalidateRegion(VAddr addr, std::size_t size) {
|
||||
std::unique_lock lock{mutex};
|
||||
@@ -118,29 +120,27 @@ public:
|
||||
*/
|
||||
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) {
|
||||
std::unique_lock lock{mutex};
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const std::optional<VAddr> cpu_addr_opt = memory_manager.GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr_opt);
|
||||
VAddr cpu_addr = *cpu_addr_opt;
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr);
|
||||
|
||||
CachedQuery* query = TryGet(cpu_addr);
|
||||
CachedQuery* query = TryGet(*cpu_addr);
|
||||
if (!query) {
|
||||
ASSERT_OR_EXECUTE(cpu_addr_opt, return;);
|
||||
const auto host_ptr = memory_manager.GetPointer(gpu_addr);
|
||||
ASSERT_OR_EXECUTE(cpu_addr, return;);
|
||||
u8* const host_ptr = gpu_memory.GetPointer(gpu_addr);
|
||||
|
||||
query = Register(type, cpu_addr, host_ptr, timestamp.has_value());
|
||||
query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
|
||||
}
|
||||
|
||||
query->BindCounter(Stream(type).Current(), timestamp);
|
||||
if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
|
||||
AsyncFlushQuery(cpu_addr);
|
||||
AsyncFlushQuery(*cpu_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
|
||||
void UpdateCounters() {
|
||||
std::unique_lock lock{mutex};
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
Stream(VideoCore::QueryType::SamplesPassed).Update(regs.samplecnt_enable);
|
||||
}
|
||||
|
||||
@@ -270,8 +270,9 @@ private:
|
||||
static constexpr std::uintptr_t PAGE_SIZE = 4096;
|
||||
static constexpr unsigned PAGE_BITS = 12;
|
||||
|
||||
Core::System& system;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
|
||||
@@ -106,11 +106,8 @@ public:
|
||||
virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
|
||||
|
||||
/// Initialize disk cached resources for the game being emulated
|
||||
virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
|
||||
const DiskResourceLoadCallback& callback = {}) {}
|
||||
|
||||
/// Initializes renderer dirty flags
|
||||
virtual void SetupDirtyFlags() {}
|
||||
virtual void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
const DiskResourceLoadCallback& callback) {}
|
||||
|
||||
/// Grant access to the Guest Driver Profile for recording/obtaining info on the guest driver.
|
||||
GuestDriverProfile& AccessGuestDriverProfile() {
|
||||
|
||||
@@ -59,9 +59,10 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst
|
||||
static_cast<GLintptr>(dst_offset), static_cast<GLsizeiptr>(size));
|
||||
}
|
||||
|
||||
OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
OGLBufferCache::OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
|
||||
const Device& device_, std::size_t stream_size)
|
||||
: GenericBufferCache{rasterizer, system,
|
||||
: GenericBufferCache{rasterizer, gpu_memory, cpu_memory,
|
||||
std::make_unique<OGLStreamBuffer>(device_, stream_size, true)},
|
||||
device{device_} {
|
||||
if (!device.HasFastBufferSubData()) {
|
||||
|
||||
@@ -52,7 +52,8 @@ private:
|
||||
using GenericBufferCache = VideoCommon::BufferCache<Buffer, GLuint, OGLStreamBuffer>;
|
||||
class OGLBufferCache final : public GenericBufferCache {
|
||||
public:
|
||||
explicit OGLBufferCache(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
explicit OGLBufferCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
|
||||
const Device& device, std::size_t stream_size);
|
||||
~OGLBufferCache();
|
||||
|
||||
|
||||
@@ -45,11 +45,10 @@ void GLInnerFence::Wait() {
|
||||
glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
|
||||
FenceManagerOpenGL::FenceManagerOpenGL(Core::System& system,
|
||||
VideoCore::RasterizerInterface& rasterizer,
|
||||
FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
|
||||
TextureCacheOpenGL& texture_cache,
|
||||
OGLBufferCache& buffer_cache, QueryCache& query_cache)
|
||||
: GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache) {}
|
||||
: GenericFenceManager{rasterizer, gpu, texture_cache, buffer_cache, query_cache} {}
|
||||
|
||||
Fence FenceManagerOpenGL::CreateFence(u32 value, bool is_stubbed) {
|
||||
return std::make_shared<GLInnerFence>(value, is_stubbed);
|
||||
|
||||
@@ -37,9 +37,9 @@ using GenericFenceManager =
|
||||
|
||||
class FenceManagerOpenGL final : public GenericFenceManager {
|
||||
public:
|
||||
FenceManagerOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
|
||||
QueryCache& query_cache);
|
||||
explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
|
||||
TextureCacheOpenGL& texture_cache, OGLBufferCache& buffer_cache,
|
||||
QueryCache& query_cache);
|
||||
|
||||
protected:
|
||||
Fence CreateFence(u32 value, bool is_stubbed) override;
|
||||
|
||||
@@ -30,12 +30,13 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
QueryCache::QueryCache(Core::System& system, RasterizerOpenGL& gl_rasterizer)
|
||||
QueryCache::QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::MemoryManager& gpu_memory)
|
||||
: VideoCommon::QueryCacheBase<
|
||||
QueryCache, CachedQuery, CounterStream, HostCounter,
|
||||
std::vector<OGLQuery>>{system,
|
||||
static_cast<VideoCore::RasterizerInterface&>(gl_rasterizer)},
|
||||
gl_rasterizer{gl_rasterizer} {}
|
||||
std::vector<OGLQuery>>{static_cast<VideoCore::RasterizerInterface&>(rasterizer),
|
||||
maxwell3d, gpu_memory},
|
||||
gl_rasterizer{rasterizer} {}
|
||||
|
||||
QueryCache::~QueryCache() = default;
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
|
||||
class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
|
||||
HostCounter, std::vector<OGLQuery>> {
|
||||
public:
|
||||
explicit QueryCache(Core::System& system, RasterizerOpenGL& rasterizer);
|
||||
explicit QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::MemoryManager& gpu_memory);
|
||||
~QueryCache();
|
||||
|
||||
OGLQuery AllocateQuery(VideoCore::QueryType type);
|
||||
|
||||
@@ -153,16 +153,19 @@ void UpdateBindlessPointers(GLenum target, GLuint64EXT* pointers, std::size_t nu
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
|
||||
const Device& device, ScreenInfo& info,
|
||||
ProgramManager& program_manager, StateTracker& state_tracker)
|
||||
: RasterizerAccelerated{system.Memory()}, device{device}, texture_cache{system, *this, device,
|
||||
state_tracker},
|
||||
shader_cache{*this, system, emu_window, device}, query_cache{system, *this},
|
||||
buffer_cache{*this, system, device, STREAM_BUFFER_SIZE},
|
||||
fence_manager{system, *this, texture_cache, buffer_cache, query_cache}, system{system},
|
||||
screen_info{info}, program_manager{program_manager}, state_tracker{state_tracker},
|
||||
async_shaders{emu_window} {
|
||||
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
|
||||
Core::Memory::Memory& cpu_memory, const Device& device_,
|
||||
ScreenInfo& screen_info_, ProgramManager& program_manager_,
|
||||
StateTracker& state_tracker_)
|
||||
: RasterizerAccelerated{cpu_memory}, gpu(gpu_), maxwell3d(gpu.Maxwell3D()),
|
||||
kepler_compute(gpu.KeplerCompute()), gpu_memory(gpu.MemoryManager()), device(device_),
|
||||
screen_info(screen_info_), program_manager(program_manager_), state_tracker(state_tracker_),
|
||||
texture_cache(*this, maxwell3d, gpu_memory, device, state_tracker),
|
||||
shader_cache(*this, emu_window, gpu, maxwell3d, kepler_compute, gpu_memory, device),
|
||||
query_cache(*this, maxwell3d, gpu_memory),
|
||||
buffer_cache(*this, gpu_memory, cpu_memory, device, STREAM_BUFFER_SIZE),
|
||||
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
|
||||
async_shaders(emu_window) {
|
||||
CheckExtensions();
|
||||
|
||||
unified_uniform_buffer.Create();
|
||||
@@ -196,8 +199,7 @@ void RasterizerOpenGL::CheckExtensions() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexFormat() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::VertexFormats]) {
|
||||
return;
|
||||
}
|
||||
@@ -217,7 +219,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
|
||||
}
|
||||
flags[Dirty::VertexFormat0 + index] = false;
|
||||
|
||||
const auto attrib = gpu.regs.vertex_attrib_format[index];
|
||||
const auto attrib = maxwell3d.regs.vertex_attrib_format[index];
|
||||
const auto gl_index = static_cast<GLuint>(index);
|
||||
|
||||
// Disable constant attributes.
|
||||
@@ -241,8 +243,7 @@ void RasterizerOpenGL::SetupVertexFormat() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexBuffer() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::VertexBuffers]) {
|
||||
return;
|
||||
}
|
||||
@@ -253,7 +254,7 @@ void RasterizerOpenGL::SetupVertexBuffer() {
|
||||
const bool use_unified_memory = device.HasVertexBufferUnifiedMemory();
|
||||
|
||||
// Upload all guest vertex arrays sequentially to our buffer
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_BINDINGS; ++index) {
|
||||
if (!flags[Dirty::VertexBuffer0 + index]) {
|
||||
continue;
|
||||
@@ -290,14 +291,13 @@ void RasterizerOpenGL::SetupVertexBuffer() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupVertexInstances() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::VertexInstances]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::VertexInstances] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
for (std::size_t index = 0; index < NUM_SUPPORTED_VERTEX_ATTRIBUTES; ++index) {
|
||||
if (!flags[Dirty::VertexInstance0 + index]) {
|
||||
continue;
|
||||
@@ -313,7 +313,7 @@ void RasterizerOpenGL::SetupVertexInstances() {
|
||||
|
||||
GLintptr RasterizerOpenGL::SetupIndexBuffer() {
|
||||
MICROPROFILE_SCOPE(OpenGL_Index);
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
const std::size_t size = CalculateIndexBufferSize();
|
||||
const auto info = buffer_cache.UploadMemory(regs.index_array.IndexStart(), size);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, info.handle);
|
||||
@@ -322,15 +322,14 @@ GLintptr RasterizerOpenGL::SetupIndexBuffer() {
|
||||
|
||||
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Shader);
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
u32 clip_distances = 0;
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const auto& shader_config = gpu.regs.shader_config[index];
|
||||
const auto& shader_config = maxwell3d.regs.shader_config[index];
|
||||
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
|
||||
// Skip stages that are not enabled
|
||||
if (!gpu.regs.IsShaderConfigEnabled(index)) {
|
||||
if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
|
||||
switch (program) {
|
||||
case Maxwell::ShaderProgram::Geometry:
|
||||
program_manager.UseGeometryShader(0);
|
||||
@@ -391,11 +390,11 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
|
||||
}
|
||||
|
||||
SyncClipEnabled(clip_distances);
|
||||
gpu.dirty.flags[Dirty::Shaders] = false;
|
||||
maxwell3d.dirty.flags[Dirty::Shaders] = false;
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
std::size_t size = 0;
|
||||
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
@@ -413,34 +412,27 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
|
||||
}
|
||||
|
||||
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
return static_cast<std::size_t>(regs.index_array.count) *
|
||||
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
|
||||
return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
|
||||
static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading,
|
||||
void RasterizerOpenGL::LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
shader_cache.LoadDiskCache(stop_loading, callback);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupDirtyFlags() {
|
||||
state_tracker.Initialize();
|
||||
shader_cache.LoadDiskCache(title_id, stop_loading, callback);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::ConfigureFramebuffers() {
|
||||
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
if (!gpu.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
|
||||
if (!maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets]) {
|
||||
return;
|
||||
}
|
||||
gpu.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::RenderTargets] = false;
|
||||
|
||||
texture_cache.GuardRenderTargets(true);
|
||||
|
||||
View depth_surface = texture_cache.GetDepthBufferSurface(true);
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
|
||||
|
||||
// Bind the framebuffer surfaces
|
||||
@@ -472,8 +464,7 @@ void RasterizerOpenGL::ConfigureFramebuffers() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_depth_stencil) {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
texture_cache.GuardRenderTargets(true);
|
||||
View color_surface;
|
||||
@@ -523,12 +514,11 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(bool using_color, bool using_de
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
if (!gpu.ShouldExecute()) {
|
||||
if (!maxwell3d.ShouldExecute()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
bool use_color{};
|
||||
bool use_depth{};
|
||||
bool use_stencil{};
|
||||
@@ -593,7 +583,6 @@ void RasterizerOpenGL::Clear() {
|
||||
|
||||
void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Drawing);
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
|
||||
query_cache.UpdateCounters();
|
||||
|
||||
@@ -641,7 +630,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
|
||||
if (invalidated) {
|
||||
// When the stream buffer has been invalidated, we have to consider vertex buffers as dirty
|
||||
auto& dirty = gpu.dirty.flags;
|
||||
auto& dirty = maxwell3d.dirty.flags;
|
||||
dirty[Dirty::VertexBuffers] = true;
|
||||
for (int index = Dirty::VertexBuffer0; index <= Dirty::VertexBuffer31; ++index) {
|
||||
dirty[index] = true;
|
||||
@@ -662,7 +651,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
// Setup emulation uniform buffer.
|
||||
if (!device.UseAssemblyShaders()) {
|
||||
MaxwellUniformData ubo;
|
||||
ubo.SetFromRegs(gpu);
|
||||
ubo.SetFromRegs(maxwell3d);
|
||||
const auto info =
|
||||
buffer_cache.UploadHostMemory(&ubo, sizeof(ubo), device.GetUniformBufferAlignment());
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, EmulationUniformBlockBinding, info.handle, info.offset,
|
||||
@@ -671,7 +660,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
|
||||
// Setup shaders and their used resources.
|
||||
texture_cache.GuardSamplers(true);
|
||||
const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(gpu.regs.draw.topology);
|
||||
const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d.regs.draw.topology);
|
||||
SetupShaders(primitive_mode);
|
||||
texture_cache.GuardSamplers(false);
|
||||
|
||||
@@ -688,14 +677,14 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
|
||||
BeginTransformFeedback(primitive_mode);
|
||||
|
||||
const GLuint base_instance = static_cast<GLuint>(gpu.regs.vb_base_instance);
|
||||
const GLuint base_instance = static_cast<GLuint>(maxwell3d.regs.vb_base_instance);
|
||||
const GLsizei num_instances =
|
||||
static_cast<GLsizei>(is_instanced ? gpu.mme_draw.instance_count : 1);
|
||||
static_cast<GLsizei>(is_instanced ? maxwell3d.mme_draw.instance_count : 1);
|
||||
if (is_indexed) {
|
||||
const GLint base_vertex = static_cast<GLint>(gpu.regs.vb_element_base);
|
||||
const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.index_array.count);
|
||||
const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vb_element_base);
|
||||
const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.index_array.count);
|
||||
const GLvoid* offset = reinterpret_cast<const GLvoid*>(index_buffer_offset);
|
||||
const GLenum format = MaxwellToGL::IndexFormat(gpu.regs.index_array.format);
|
||||
const GLenum format = MaxwellToGL::IndexFormat(maxwell3d.regs.index_array.format);
|
||||
if (num_instances == 1 && base_instance == 0 && base_vertex == 0) {
|
||||
glDrawElements(primitive_mode, num_vertices, format, offset);
|
||||
} else if (num_instances == 1 && base_instance == 0) {
|
||||
@@ -714,8 +703,8 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
base_instance);
|
||||
}
|
||||
} else {
|
||||
const GLint base_vertex = static_cast<GLint>(gpu.regs.vertex_buffer.first);
|
||||
const GLsizei num_vertices = static_cast<GLsizei>(gpu.regs.vertex_buffer.count);
|
||||
const GLint base_vertex = static_cast<GLint>(maxwell3d.regs.vertex_buffer.first);
|
||||
const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d.regs.vertex_buffer.count);
|
||||
if (num_instances == 1 && base_instance == 0) {
|
||||
glDrawArrays(primitive_mode, base_vertex, num_vertices);
|
||||
} else if (base_instance == 0) {
|
||||
@@ -730,7 +719,7 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
|
||||
|
||||
++num_queued_commands;
|
||||
|
||||
system.GPU().TickWork();
|
||||
gpu.TickWork();
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
|
||||
@@ -753,7 +742,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
|
||||
|
||||
buffer_cache.Unmap();
|
||||
|
||||
const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
|
||||
const auto& launch_desc = kepler_compute.launch_description;
|
||||
program_manager.BindCompute(kernel->GetHandle());
|
||||
glDispatchCompute(launch_desc.grid_dim_x, launch_desc.grid_dim_y, launch_desc.grid_dim_z);
|
||||
++num_queued_commands;
|
||||
}
|
||||
@@ -815,17 +805,14 @@ void RasterizerOpenGL::SyncGuestHost() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SignalSemaphore(GPUVAddr addr, u32 value) {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
memory_manager.Write<u32>(addr, value);
|
||||
gpu_memory.Write<u32>(addr, value);
|
||||
return;
|
||||
}
|
||||
fence_manager.SignalSemaphore(addr, value);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SignalSyncPoint(u32 value) {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
gpu.IncrementSyncPoint(value);
|
||||
return;
|
||||
@@ -834,7 +821,6 @@ void RasterizerOpenGL::SignalSyncPoint(u32 value) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::ReleaseFences() {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
return;
|
||||
}
|
||||
@@ -920,7 +906,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh
|
||||
GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV};
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_UBO);
|
||||
const auto& stages = system.GPU().Maxwell3D().state.shader_stages;
|
||||
const auto& stages = maxwell3d.state.shader_stages;
|
||||
const auto& shader_stage = stages[stage_index];
|
||||
const auto& entries = shader->GetEntries();
|
||||
const bool use_unified = entries.use_unified_uniforms;
|
||||
@@ -945,7 +931,7 @@ void RasterizerOpenGL::SetupDrawConstBuffers(std::size_t stage_index, Shader* sh
|
||||
|
||||
void RasterizerOpenGL::SetupComputeConstBuffers(Shader* kernel) {
|
||||
MICROPROFILE_SCOPE(OpenGL_UBO);
|
||||
const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
|
||||
const auto& launch_desc = kepler_compute.launch_description;
|
||||
const auto& entries = kernel->GetEntries();
|
||||
const bool use_unified = entries.use_unified_uniforms;
|
||||
|
||||
@@ -1018,9 +1004,7 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
|
||||
GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
|
||||
};
|
||||
|
||||
auto& gpu{system.GPU()};
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
const auto& cbufs{gpu.Maxwell3D().state.shader_stages[stage_index]};
|
||||
const auto& cbufs{maxwell3d.state.shader_stages[stage_index]};
|
||||
const auto& entries{shader->GetEntries().global_memory_entries};
|
||||
|
||||
std::array<GLuint64EXT, 32> pointers;
|
||||
@@ -1030,8 +1014,8 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
|
||||
u32 binding = assembly_shaders ? 0 : device.GetBaseBindings(stage_index).shader_storage_buffer;
|
||||
for (const auto& entry : entries) {
|
||||
const GPUVAddr addr{cbufs.const_buffers[entry.cbuf_index].address + entry.cbuf_offset};
|
||||
const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
|
||||
const u32 size{memory_manager.Read<u32>(addr + 8)};
|
||||
const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
|
||||
const u32 size{gpu_memory.Read<u32>(addr + 8)};
|
||||
SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
|
||||
++binding;
|
||||
}
|
||||
@@ -1041,9 +1025,7 @@ void RasterizerOpenGL::SetupDrawGlobalMemory(std::size_t stage_index, Shader* sh
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
|
||||
auto& gpu{system.GPU()};
|
||||
auto& memory_manager{gpu.MemoryManager()};
|
||||
const auto& cbufs{gpu.KeplerCompute().launch_description.const_buffer_config};
|
||||
const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
|
||||
const auto& entries{kernel->GetEntries().global_memory_entries};
|
||||
|
||||
std::array<GLuint64EXT, 32> pointers;
|
||||
@@ -1052,8 +1034,8 @@ void RasterizerOpenGL::SetupComputeGlobalMemory(Shader* kernel) {
|
||||
u32 binding = 0;
|
||||
for (const auto& entry : entries) {
|
||||
const GPUVAddr addr{cbufs[entry.cbuf_index].Address() + entry.cbuf_offset};
|
||||
const GPUVAddr gpu_addr{memory_manager.Read<u64>(addr)};
|
||||
const u32 size{memory_manager.Read<u32>(addr + 8)};
|
||||
const GPUVAddr gpu_addr{gpu_memory.Read<u64>(addr)};
|
||||
const u32 size{gpu_memory.Read<u32>(addr + 8)};
|
||||
SetupGlobalMemory(binding, entry, gpu_addr, size, &pointers[binding]);
|
||||
++binding;
|
||||
}
|
||||
@@ -1077,7 +1059,6 @@ void RasterizerOpenGL::SetupGlobalMemory(u32 binding, const GlobalMemoryEntry& e
|
||||
|
||||
void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture);
|
||||
const auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
u32 binding = device.GetBaseBindings(stage_index).sampler;
|
||||
for (const auto& entry : shader->GetEntries().samplers) {
|
||||
const auto shader_type = static_cast<ShaderType>(stage_index);
|
||||
@@ -1090,11 +1071,10 @@ void RasterizerOpenGL::SetupDrawTextures(std::size_t stage_index, Shader* shader
|
||||
|
||||
void RasterizerOpenGL::SetupComputeTextures(Shader* kernel) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture);
|
||||
const auto& compute = system.GPU().KeplerCompute();
|
||||
u32 binding = 0;
|
||||
for (const auto& entry : kernel->GetEntries().samplers) {
|
||||
for (std::size_t i = 0; i < entry.size; ++i) {
|
||||
const auto texture = GetTextureInfo(compute, entry, ShaderType::Compute, i);
|
||||
const auto texture = GetTextureInfo(kepler_compute, entry, ShaderType::Compute, i);
|
||||
SetupTexture(binding++, texture, entry);
|
||||
}
|
||||
}
|
||||
@@ -1118,20 +1098,18 @@ void RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextu
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupDrawImages(std::size_t stage_index, Shader* shader) {
|
||||
const auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
u32 binding = device.GetBaseBindings(stage_index).image;
|
||||
for (const auto& entry : shader->GetEntries().images) {
|
||||
const auto shader_type = static_cast<Tegra::Engines::ShaderType>(stage_index);
|
||||
const auto shader_type = static_cast<ShaderType>(stage_index);
|
||||
const auto tic = GetTextureInfo(maxwell3d, entry, shader_type).tic;
|
||||
SetupImage(binding++, tic, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SetupComputeImages(Shader* shader) {
|
||||
const auto& compute = system.GPU().KeplerCompute();
|
||||
u32 binding = 0;
|
||||
for (const auto& entry : shader->GetEntries().images) {
|
||||
const auto tic = GetTextureInfo(compute, entry, Tegra::Engines::ShaderType::Compute).tic;
|
||||
const auto tic = GetTextureInfo(kepler_compute, entry, ShaderType::Compute).tic;
|
||||
SetupImage(binding++, tic, entry);
|
||||
}
|
||||
}
|
||||
@@ -1151,9 +1129,8 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncViewport() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
const auto& regs = gpu.regs;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
const bool dirty_viewport = flags[Dirty::Viewports];
|
||||
const bool dirty_clip_control = flags[Dirty::ClipControl];
|
||||
@@ -1225,25 +1202,23 @@ void RasterizerOpenGL::SyncViewport() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncDepthClamp() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::DepthClampEnabled]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::DepthClampEnabled] = false;
|
||||
|
||||
oglEnable(GL_DEPTH_CLAMP, gpu.regs.view_volume_clip_control.depth_clamp_disabled == 0);
|
||||
oglEnable(GL_DEPTH_CLAMP, maxwell3d.regs.view_volume_clip_control.depth_clamp_disabled == 0);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncClipEnabled(u32 clip_mask) {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::ClipDistances] && !flags[Dirty::Shaders]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::ClipDistances] = false;
|
||||
|
||||
clip_mask &= gpu.regs.clip_distance_enabled;
|
||||
clip_mask &= maxwell3d.regs.clip_distance_enabled;
|
||||
if (clip_mask == last_clip_distance_mask) {
|
||||
return;
|
||||
}
|
||||
@@ -1259,9 +1234,8 @@ void RasterizerOpenGL::SyncClipCoef() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncCullMode() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
const auto& regs = gpu.regs;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
if (flags[Dirty::CullTest]) {
|
||||
flags[Dirty::CullTest] = false;
|
||||
@@ -1276,26 +1250,24 @@ void RasterizerOpenGL::SyncCullMode() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPrimitiveRestart() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::PrimitiveRestart]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::PrimitiveRestart] = false;
|
||||
|
||||
if (gpu.regs.primitive_restart.enabled) {
|
||||
if (maxwell3d.regs.primitive_restart.enabled) {
|
||||
glEnable(GL_PRIMITIVE_RESTART);
|
||||
glPrimitiveRestartIndex(gpu.regs.primitive_restart.index);
|
||||
glPrimitiveRestartIndex(maxwell3d.regs.primitive_restart.index);
|
||||
} else {
|
||||
glDisable(GL_PRIMITIVE_RESTART);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncDepthTestState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
if (flags[Dirty::DepthMask]) {
|
||||
flags[Dirty::DepthMask] = false;
|
||||
glDepthMask(regs.depth_write_enabled ? GL_TRUE : GL_FALSE);
|
||||
@@ -1313,14 +1285,13 @@ void RasterizerOpenGL::SyncDepthTestState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncStencilTestState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::StencilTest]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::StencilTest] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
oglEnable(GL_STENCIL_TEST, regs.stencil_enable);
|
||||
|
||||
glStencilFuncSeparate(GL_FRONT, MaxwellToGL::ComparisonOp(regs.stencil_front_func_func),
|
||||
@@ -1345,25 +1316,24 @@ void RasterizerOpenGL::SyncStencilTestState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncRasterizeEnable() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::RasterizeEnable]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::RasterizeEnable] = false;
|
||||
|
||||
oglEnable(GL_RASTERIZER_DISCARD, gpu.regs.rasterize_enable == 0);
|
||||
oglEnable(GL_RASTERIZER_DISCARD, maxwell3d.regs.rasterize_enable == 0);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPolygonModes() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::PolygonModes]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::PolygonModes] = false;
|
||||
|
||||
if (gpu.regs.fill_rectangle) {
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.fill_rectangle) {
|
||||
if (!GLAD_GL_NV_fill_rectangle) {
|
||||
LOG_ERROR(Render_OpenGL, "GL_NV_fill_rectangle used and not supported");
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
@@ -1376,27 +1346,26 @@ void RasterizerOpenGL::SyncPolygonModes() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gpu.regs.polygon_mode_front == gpu.regs.polygon_mode_back) {
|
||||
if (regs.polygon_mode_front == regs.polygon_mode_back) {
|
||||
flags[Dirty::PolygonModeFront] = false;
|
||||
flags[Dirty::PolygonModeBack] = false;
|
||||
glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
|
||||
glPolygonMode(GL_FRONT_AND_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
|
||||
return;
|
||||
}
|
||||
|
||||
if (flags[Dirty::PolygonModeFront]) {
|
||||
flags[Dirty::PolygonModeFront] = false;
|
||||
glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_front));
|
||||
glPolygonMode(GL_FRONT, MaxwellToGL::PolygonMode(regs.polygon_mode_front));
|
||||
}
|
||||
|
||||
if (flags[Dirty::PolygonModeBack]) {
|
||||
flags[Dirty::PolygonModeBack] = false;
|
||||
glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(gpu.regs.polygon_mode_back));
|
||||
glPolygonMode(GL_BACK, MaxwellToGL::PolygonMode(regs.polygon_mode_back));
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncColorMask() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::ColorMasks]) {
|
||||
return;
|
||||
}
|
||||
@@ -1405,7 +1374,7 @@ void RasterizerOpenGL::SyncColorMask() {
|
||||
const bool force = flags[Dirty::ColorMaskCommon];
|
||||
flags[Dirty::ColorMaskCommon] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.color_mask_common) {
|
||||
if (!force && !flags[Dirty::ColorMask0]) {
|
||||
return;
|
||||
@@ -1430,33 +1399,30 @@ void RasterizerOpenGL::SyncColorMask() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncMultiSampleState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::MultisampleControl]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::MultisampleControl] = false;
|
||||
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
oglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE, regs.multisample_control.alpha_to_coverage);
|
||||
oglEnable(GL_SAMPLE_ALPHA_TO_ONE, regs.multisample_control.alpha_to_one);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncFragmentColorClampState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::FragmentClampColor]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::FragmentClampColor] = false;
|
||||
|
||||
glClampColor(GL_CLAMP_FRAGMENT_COLOR, gpu.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
|
||||
glClampColor(GL_CLAMP_FRAGMENT_COLOR, maxwell3d.regs.frag_color_clamp ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncBlendState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
const auto& regs = gpu.regs;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
if (flags[Dirty::BlendColor]) {
|
||||
flags[Dirty::BlendColor] = false;
|
||||
@@ -1513,14 +1479,13 @@ void RasterizerOpenGL::SyncBlendState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLogicOpState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::LogicOp]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::LogicOp] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.logic_op.enable) {
|
||||
glEnable(GL_COLOR_LOGIC_OP);
|
||||
glLogicOp(MaxwellToGL::LogicOp(regs.logic_op.operation));
|
||||
@@ -1530,14 +1495,13 @@ void RasterizerOpenGL::SyncLogicOpState() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncScissorTest() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::Scissors]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::Scissors] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
for (std::size_t index = 0; index < Maxwell::NumViewports; ++index) {
|
||||
if (!flags[Dirty::Scissor0 + index]) {
|
||||
continue;
|
||||
@@ -1556,16 +1520,15 @@ void RasterizerOpenGL::SyncScissorTest() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPointState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::PointSize]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::PointSize] = false;
|
||||
|
||||
oglEnable(GL_POINT_SPRITE, gpu.regs.point_sprite_enable);
|
||||
oglEnable(GL_POINT_SPRITE, maxwell3d.regs.point_sprite_enable);
|
||||
|
||||
if (gpu.regs.vp_point_size.enable) {
|
||||
if (maxwell3d.regs.vp_point_size.enable) {
|
||||
// By definition of GL_POINT_SIZE, it only matters if GL_PROGRAM_POINT_SIZE is disabled.
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
return;
|
||||
@@ -1573,32 +1536,30 @@ void RasterizerOpenGL::SyncPointState() {
|
||||
|
||||
// Limit the point size to 1 since nouveau sometimes sets a point size of 0 (and that's invalid
|
||||
// in OpenGL).
|
||||
glPointSize(std::max(1.0f, gpu.regs.point_size));
|
||||
glPointSize(std::max(1.0f, maxwell3d.regs.point_size));
|
||||
glDisable(GL_PROGRAM_POINT_SIZE);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLineState() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::LineWidth]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::LineWidth] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
oglEnable(GL_LINE_SMOOTH, regs.line_smooth_enable);
|
||||
glLineWidth(regs.line_smooth_enable ? regs.line_width_smooth : regs.line_width_aliased);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncPolygonOffset() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::PolygonOffset]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::PolygonOffset] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
oglEnable(GL_POLYGON_OFFSET_FILL, regs.polygon_offset_fill_enable);
|
||||
oglEnable(GL_POLYGON_OFFSET_LINE, regs.polygon_offset_line_enable);
|
||||
oglEnable(GL_POLYGON_OFFSET_POINT, regs.polygon_offset_point_enable);
|
||||
@@ -1612,14 +1573,13 @@ void RasterizerOpenGL::SyncPolygonOffset() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncAlphaTest() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::AlphaTest]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::AlphaTest] = false;
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.alpha_test_enabled && regs.rt_control.count > 1) {
|
||||
LOG_WARNING(Render_OpenGL, "Alpha testing with more than one render target is not tested");
|
||||
}
|
||||
@@ -1633,20 +1593,19 @@ void RasterizerOpenGL::SyncAlphaTest() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncFramebufferSRGB() {
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
auto& flags = gpu.dirty.flags;
|
||||
auto& flags = maxwell3d.dirty.flags;
|
||||
if (!flags[Dirty::FramebufferSRGB]) {
|
||||
return;
|
||||
}
|
||||
flags[Dirty::FramebufferSRGB] = false;
|
||||
|
||||
oglEnable(GL_FRAMEBUFFER_SRGB, gpu.regs.framebuffer_srgb);
|
||||
oglEnable(GL_FRAMEBUFFER_SRGB, maxwell3d.regs.framebuffer_srgb);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
// TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
|
||||
// when this is required.
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
static constexpr std::size_t STRIDE = 3;
|
||||
std::array<GLint, 128 * STRIDE * Maxwell::NumTransformFeedbackBuffers> attribs;
|
||||
@@ -1698,7 +1657,7 @@ void RasterizerOpenGL::SyncTransformFeedback() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.tfb_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1741,7 +1700,7 @@ void RasterizerOpenGL::BeginTransformFeedback(GLenum primitive_mode) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::EndTransformFeedback() {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.tfb_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
#include "video_core/shader/async_shaders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Core::Frontend {
|
||||
@@ -55,9 +55,10 @@ struct DrawParameters;
|
||||
|
||||
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
|
||||
public:
|
||||
explicit RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
|
||||
const Device& device, ScreenInfo& info,
|
||||
ProgramManager& program_manager, StateTracker& state_tracker);
|
||||
explicit RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
|
||||
Core::Memory::Memory& cpu_memory, const Device& device,
|
||||
ScreenInfo& screen_info, ProgramManager& program_manager,
|
||||
StateTracker& state_tracker);
|
||||
~RasterizerOpenGL() override;
|
||||
|
||||
void Draw(bool is_indexed, bool is_instanced) override;
|
||||
@@ -83,9 +84,8 @@ public:
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
void LoadDiskResources(const std::atomic_bool& stop_loading,
|
||||
void LoadDiskResources(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) override;
|
||||
void SetupDirtyFlags() override;
|
||||
|
||||
/// Returns true when there are commands queued to the OpenGL server.
|
||||
bool AnyCommandQueued() const {
|
||||
@@ -237,7 +237,15 @@ private:
|
||||
|
||||
void SetupShaders(GLenum primitive_mode);
|
||||
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
const Device& device;
|
||||
ScreenInfo& screen_info;
|
||||
ProgramManager& program_manager;
|
||||
StateTracker& state_tracker;
|
||||
|
||||
TextureCacheOpenGL texture_cache;
|
||||
ShaderCacheOpenGL shader_cache;
|
||||
@@ -247,10 +255,6 @@ private:
|
||||
OGLBufferCache buffer_cache;
|
||||
FenceManagerOpenGL fence_manager;
|
||||
|
||||
Core::System& system;
|
||||
ScreenInfo& screen_info;
|
||||
ProgramManager& program_manager;
|
||||
StateTracker& state_tracker;
|
||||
VideoCommon::Shader::AsyncShaders async_shaders;
|
||||
|
||||
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||
|
||||
@@ -239,12 +239,11 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
|
||||
ProgramCode code_b, VideoCommon::Shader::AsyncShaders& async_shaders, VAddr cpu_addr) {
|
||||
const auto shader_type = GetShaderType(program_type);
|
||||
|
||||
auto& gpu = params.system.GPU();
|
||||
auto& gpu = params.gpu;
|
||||
gpu.ShaderNotify().MarkSharderBuilding();
|
||||
|
||||
auto registry = std::make_shared<Registry>(shader_type, gpu.Maxwell3D());
|
||||
if (!async_shaders.IsShaderAsync(params.system.GPU()) ||
|
||||
!params.device.UseAsynchronousShaders()) {
|
||||
if (!async_shaders.IsShaderAsync(gpu) || !params.device.UseAsynchronousShaders()) {
|
||||
const ShaderIR ir(code, STAGE_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
|
||||
// TODO(Rodrigo): Handle VertexA shaders
|
||||
// std::optional<ShaderIR> ir_b;
|
||||
@@ -287,11 +286,10 @@ std::unique_ptr<Shader> Shader::CreateStageFromMemory(
|
||||
|
||||
std::unique_ptr<Shader> Shader::CreateKernelFromMemory(const ShaderParameters& params,
|
||||
ProgramCode code) {
|
||||
auto& gpu = params.system.GPU();
|
||||
auto& gpu = params.gpu;
|
||||
gpu.ShaderNotify().MarkSharderBuilding();
|
||||
|
||||
auto& engine = gpu.KeplerCompute();
|
||||
auto registry = std::make_shared<Registry>(ShaderType::Compute, engine);
|
||||
auto registry = std::make_shared<Registry>(ShaderType::Compute, params.engine);
|
||||
const ShaderIR ir(code, KERNEL_MAIN_OFFSET, COMPILER_SETTINGS, *registry);
|
||||
const u64 uid = params.unique_identifier;
|
||||
auto program = BuildShader(params.device, ShaderType::Compute, uid, ir, *registry);
|
||||
@@ -320,15 +318,20 @@ std::unique_ptr<Shader> Shader::CreateFromCache(const ShaderParameters& params,
|
||||
precompiled_shader.registry, precompiled_shader.entries, precompiled_shader.program));
|
||||
}
|
||||
|
||||
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
Core::Frontend::EmuWindow& emu_window, const Device& device)
|
||||
: VideoCommon::ShaderCache<Shader>{rasterizer}, system{system},
|
||||
emu_window{emu_window}, device{device}, disk_cache{system} {}
|
||||
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer,
|
||||
Core::Frontend::EmuWindow& emu_window_, Tegra::GPU& gpu_,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
||||
Tegra::Engines::KeplerCompute& kepler_compute_,
|
||||
Tegra::MemoryManager& gpu_memory_, const Device& device_)
|
||||
: VideoCommon::ShaderCache<Shader>{rasterizer}, emu_window{emu_window_}, gpu{gpu_},
|
||||
gpu_memory{gpu_memory_}, maxwell3d{maxwell3d_},
|
||||
kepler_compute{kepler_compute_}, device{device_} {}
|
||||
|
||||
ShaderCacheOpenGL::~ShaderCacheOpenGL() = default;
|
||||
|
||||
void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
void ShaderCacheOpenGL::LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
disk_cache.BindTitleID(title_id);
|
||||
const std::optional transferable = disk_cache.LoadTransferable();
|
||||
if (!transferable) {
|
||||
return;
|
||||
@@ -481,21 +484,19 @@ ProgramSharedPtr ShaderCacheOpenGL::GeneratePrecompiledProgram(
|
||||
|
||||
Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
|
||||
VideoCommon::Shader::AsyncShaders& async_shaders) {
|
||||
if (!system.GPU().Maxwell3D().dirty.flags[Dirty::Shaders]) {
|
||||
if (!maxwell3d.dirty.flags[Dirty::Shaders]) {
|
||||
auto* last_shader = last_shaders[static_cast<std::size_t>(program)];
|
||||
if (last_shader->IsBuilt()) {
|
||||
return last_shader;
|
||||
}
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const GPUVAddr address{GetShaderAddress(system, program)};
|
||||
const GPUVAddr address{GetShaderAddress(maxwell3d, program)};
|
||||
|
||||
if (device.UseAsynchronousShaders() && async_shaders.HasCompletedWork()) {
|
||||
auto completed_work = async_shaders.GetCompletedWork();
|
||||
for (auto& work : completed_work) {
|
||||
Shader* shader = TryGet(work.cpu_address);
|
||||
auto& gpu = system.GPU();
|
||||
gpu.ShaderNotify().MarkShaderComplete();
|
||||
if (shader == nullptr) {
|
||||
continue;
|
||||
@@ -507,14 +508,13 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
|
||||
shader->AsyncGLASMBuilt(std::move(work.program.glasm));
|
||||
}
|
||||
|
||||
auto& registry = shader->GetRegistry();
|
||||
|
||||
ShaderDiskCacheEntry entry;
|
||||
entry.type = work.shader_type;
|
||||
entry.code = std::move(work.code);
|
||||
entry.code_b = std::move(work.code_b);
|
||||
entry.unique_identifier = work.uid;
|
||||
|
||||
auto& registry = shader->GetRegistry();
|
||||
|
||||
entry.bound_buffer = registry.GetBoundBuffer();
|
||||
entry.graphics_info = registry.GetGraphicsInfo();
|
||||
entry.keys = registry.GetKeys();
|
||||
@@ -525,28 +525,28 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
|
||||
}
|
||||
|
||||
// Look up shader in the cache based on address
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(address)};
|
||||
const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(address)};
|
||||
if (Shader* const shader{cpu_addr ? TryGet(*cpu_addr) : null_shader.get()}) {
|
||||
return last_shaders[static_cast<std::size_t>(program)] = shader;
|
||||
}
|
||||
|
||||
const auto host_ptr{memory_manager.GetPointer(address)};
|
||||
const u8* const host_ptr{gpu_memory.GetPointer(address)};
|
||||
|
||||
// No shader found - create a new one
|
||||
ProgramCode code{GetShaderCode(memory_manager, address, host_ptr, false)};
|
||||
ProgramCode code{GetShaderCode(gpu_memory, address, host_ptr, false)};
|
||||
ProgramCode code_b;
|
||||
if (program == Maxwell::ShaderProgram::VertexA) {
|
||||
const GPUVAddr address_b{GetShaderAddress(system, Maxwell::ShaderProgram::VertexB)};
|
||||
const u8* host_ptr_b = memory_manager.GetPointer(address_b);
|
||||
code_b = GetShaderCode(memory_manager, address_b, host_ptr_b, false);
|
||||
const GPUVAddr address_b{GetShaderAddress(maxwell3d, Maxwell::ShaderProgram::VertexB)};
|
||||
const u8* host_ptr_b = gpu_memory.GetPointer(address_b);
|
||||
code_b = GetShaderCode(gpu_memory, address_b, host_ptr_b, false);
|
||||
}
|
||||
const std::size_t code_size = code.size() * sizeof(u64);
|
||||
|
||||
const u64 unique_identifier = GetUniqueIdentifier(
|
||||
GetShaderType(program), program == Maxwell::ShaderProgram::VertexA, code, code_b);
|
||||
|
||||
const ShaderParameters params{system, disk_cache, device,
|
||||
*cpu_addr, host_ptr, unique_identifier};
|
||||
const ShaderParameters params{gpu, maxwell3d, disk_cache, device,
|
||||
*cpu_addr, host_ptr, unique_identifier};
|
||||
|
||||
std::unique_ptr<Shader> shader;
|
||||
const auto found = runtime_cache.find(unique_identifier);
|
||||
@@ -568,21 +568,20 @@ Shader* ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program,
|
||||
}
|
||||
|
||||
Shader* ShaderCacheOpenGL::GetComputeKernel(GPUVAddr code_addr) {
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(code_addr)};
|
||||
const std::optional<VAddr> cpu_addr{gpu_memory.GpuToCpuAddress(code_addr)};
|
||||
|
||||
if (Shader* const kernel = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get()) {
|
||||
return kernel;
|
||||
}
|
||||
|
||||
const auto host_ptr{memory_manager.GetPointer(code_addr)};
|
||||
// No kernel found, create a new one
|
||||
ProgramCode code{GetShaderCode(memory_manager, code_addr, host_ptr, true)};
|
||||
const u8* host_ptr{gpu_memory.GetPointer(code_addr)};
|
||||
ProgramCode code{GetShaderCode(gpu_memory, code_addr, host_ptr, true)};
|
||||
const std::size_t code_size{code.size() * sizeof(u64)};
|
||||
const u64 unique_identifier{GetUniqueIdentifier(ShaderType::Compute, false, code)};
|
||||
|
||||
const ShaderParameters params{system, disk_cache, device,
|
||||
*cpu_addr, host_ptr, unique_identifier};
|
||||
const ShaderParameters params{gpu, kepler_compute, disk_cache, device,
|
||||
*cpu_addr, host_ptr, unique_identifier};
|
||||
|
||||
std::unique_ptr<Shader> kernel;
|
||||
const auto found = runtime_cache.find(unique_identifier);
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#include "video_core/shader/shader_ir.h"
|
||||
#include "video_core/shader_cache.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Core::Frontend {
|
||||
@@ -57,11 +57,12 @@ struct PrecompiledShader {
|
||||
};
|
||||
|
||||
struct ShaderParameters {
|
||||
Core::System& system;
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::Engines::ConstBufferEngineInterface& engine;
|
||||
ShaderDiskCacheOpenGL& disk_cache;
|
||||
const Device& device;
|
||||
VAddr cpu_addr;
|
||||
u8* host_ptr;
|
||||
const u8* host_ptr;
|
||||
u64 unique_identifier;
|
||||
};
|
||||
|
||||
@@ -118,12 +119,14 @@ private:
|
||||
|
||||
class ShaderCacheOpenGL final : public VideoCommon::ShaderCache<Shader> {
|
||||
public:
|
||||
explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system,
|
||||
Core::Frontend::EmuWindow& emu_window, const Device& device);
|
||||
explicit ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::Frontend::EmuWindow& emu_window,
|
||||
Tegra::GPU& gpu, Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
Tegra::MemoryManager& gpu_memory, const Device& device);
|
||||
~ShaderCacheOpenGL() override;
|
||||
|
||||
/// Loads disk cache for the current game
|
||||
void LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
void LoadDiskCache(u64 title_id, const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback);
|
||||
|
||||
/// Gets the current specified shader stage program
|
||||
@@ -138,9 +141,13 @@ private:
|
||||
const ShaderDiskCacheEntry& entry, const ShaderDiskCachePrecompiled& precompiled_entry,
|
||||
const std::unordered_set<GLenum>& supported_formats);
|
||||
|
||||
Core::System& system;
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
const Device& device;
|
||||
|
||||
ShaderDiskCacheOpenGL disk_cache;
|
||||
std::unordered_map<u64, PrecompiledShader> runtime_cache;
|
||||
|
||||
|
||||
@@ -206,13 +206,17 @@ bool ShaderDiskCacheEntry::Save(Common::FS::IOFile& file) const {
|
||||
flat_bindless_samplers.size();
|
||||
}
|
||||
|
||||
ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL(Core::System& system) : system{system} {}
|
||||
ShaderDiskCacheOpenGL::ShaderDiskCacheOpenGL() = default;
|
||||
|
||||
ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
|
||||
|
||||
void ShaderDiskCacheOpenGL::BindTitleID(u64 title_id_) {
|
||||
title_id = title_id_;
|
||||
}
|
||||
|
||||
std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() {
|
||||
// Skip games without title id
|
||||
const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
|
||||
const bool has_title_id = title_id != 0;
|
||||
if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -474,7 +478,7 @@ std::string ShaderDiskCacheOpenGL::GetBaseDir() const {
|
||||
}
|
||||
|
||||
std::string ShaderDiskCacheOpenGL::GetTitleID() const {
|
||||
return fmt::format("{:016X}", system.CurrentProcess()->GetTitleID());
|
||||
return fmt::format("{:016X}", title_id);
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
#include "video_core/engines/shader_type.h"
|
||||
#include "video_core/shader/registry.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Common::FS {
|
||||
class IOFile;
|
||||
}
|
||||
@@ -70,9 +66,12 @@ struct ShaderDiskCachePrecompiled {
|
||||
|
||||
class ShaderDiskCacheOpenGL {
|
||||
public:
|
||||
explicit ShaderDiskCacheOpenGL(Core::System& system);
|
||||
explicit ShaderDiskCacheOpenGL();
|
||||
~ShaderDiskCacheOpenGL();
|
||||
|
||||
/// Binds a title ID for all future operations.
|
||||
void BindTitleID(u64 title_id);
|
||||
|
||||
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
|
||||
std::optional<std::vector<ShaderDiskCacheEntry>> LoadTransferable();
|
||||
|
||||
@@ -157,8 +156,6 @@ private:
|
||||
return LoadArrayFromPrecompiled(&object, 1);
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
|
||||
// Stores whole precompiled cache which will be read from or saved to the precompiled chache
|
||||
// file
|
||||
FileSys::VectorVfsFile precompiled_cache_virtual_file;
|
||||
@@ -168,8 +165,11 @@ private:
|
||||
// Stored transferable shaders
|
||||
std::unordered_set<u64> stored_transferable;
|
||||
|
||||
/// Title ID to operate on
|
||||
u64 title_id = 0;
|
||||
|
||||
// The cache has been loaded at boot
|
||||
bool is_usable{};
|
||||
bool is_usable = false;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -214,10 +214,8 @@ void SetupDirtyMisc(Tables& tables) {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
StateTracker::StateTracker(Core::System& system) : system{system} {}
|
||||
|
||||
void StateTracker::Initialize() {
|
||||
auto& dirty = system.GPU().Maxwell3D().dirty;
|
||||
StateTracker::StateTracker(Tegra::GPU& gpu) : flags{gpu.Maxwell3D().dirty.flags} {
|
||||
auto& dirty = gpu.Maxwell3D().dirty;
|
||||
auto& tables = dirty.tables;
|
||||
SetupDirtyRenderTargets(tables);
|
||||
SetupDirtyColorMasks(tables);
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
#include "video_core/dirty_flags.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
namespace Tegra {
|
||||
class GPU;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
@@ -90,9 +90,7 @@ static_assert(Last <= std::numeric_limits<u8>::max());
|
||||
|
||||
class StateTracker {
|
||||
public:
|
||||
explicit StateTracker(Core::System& system);
|
||||
|
||||
void Initialize();
|
||||
explicit StateTracker(Tegra::GPU& gpu);
|
||||
|
||||
void BindIndexBuffer(GLuint new_index_buffer) {
|
||||
if (index_buffer == new_index_buffer) {
|
||||
@@ -103,7 +101,6 @@ public:
|
||||
}
|
||||
|
||||
void NotifyScreenDrawVertexArray() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::VertexFormats] = true;
|
||||
flags[OpenGL::Dirty::VertexFormat0 + 0] = true;
|
||||
flags[OpenGL::Dirty::VertexFormat0 + 1] = true;
|
||||
@@ -117,98 +114,81 @@ public:
|
||||
}
|
||||
|
||||
void NotifyPolygonModes() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::PolygonModes] = true;
|
||||
flags[OpenGL::Dirty::PolygonModeFront] = true;
|
||||
flags[OpenGL::Dirty::PolygonModeBack] = true;
|
||||
}
|
||||
|
||||
void NotifyViewport0() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::Viewports] = true;
|
||||
flags[OpenGL::Dirty::Viewport0] = true;
|
||||
}
|
||||
|
||||
void NotifyScissor0() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::Scissors] = true;
|
||||
flags[OpenGL::Dirty::Scissor0] = true;
|
||||
}
|
||||
|
||||
void NotifyColorMask0() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::ColorMasks] = true;
|
||||
flags[OpenGL::Dirty::ColorMask0] = true;
|
||||
}
|
||||
|
||||
void NotifyBlend0() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::BlendStates] = true;
|
||||
flags[OpenGL::Dirty::BlendState0] = true;
|
||||
}
|
||||
|
||||
void NotifyFramebuffer() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[VideoCommon::Dirty::RenderTargets] = true;
|
||||
}
|
||||
|
||||
void NotifyFrontFace() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::FrontFace] = true;
|
||||
}
|
||||
|
||||
void NotifyCullTest() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::CullTest] = true;
|
||||
}
|
||||
|
||||
void NotifyDepthMask() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::DepthMask] = true;
|
||||
}
|
||||
|
||||
void NotifyDepthTest() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::DepthTest] = true;
|
||||
}
|
||||
|
||||
void NotifyStencilTest() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::StencilTest] = true;
|
||||
}
|
||||
|
||||
void NotifyPolygonOffset() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::PolygonOffset] = true;
|
||||
}
|
||||
|
||||
void NotifyRasterizeEnable() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::RasterizeEnable] = true;
|
||||
}
|
||||
|
||||
void NotifyFramebufferSRGB() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::FramebufferSRGB] = true;
|
||||
}
|
||||
|
||||
void NotifyLogicOp() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::LogicOp] = true;
|
||||
}
|
||||
|
||||
void NotifyClipControl() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::ClipControl] = true;
|
||||
}
|
||||
|
||||
void NotifyAlphaTest() {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
flags[OpenGL::Dirty::AlphaTest] = true;
|
||||
}
|
||||
|
||||
private:
|
||||
Core::System& system;
|
||||
Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
|
||||
|
||||
GLuint index_buffer = 0;
|
||||
};
|
||||
|
||||
@@ -532,10 +532,12 @@ OGLTextureView CachedSurfaceView::CreateTextureView() const {
|
||||
return texture_view;
|
||||
}
|
||||
|
||||
TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system,
|
||||
VideoCore::RasterizerInterface& rasterizer,
|
||||
const Device& device, StateTracker& state_tracker)
|
||||
: TextureCacheBase{system, rasterizer, device.HasASTC()}, state_tracker{state_tracker} {
|
||||
TextureCacheOpenGL::TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::MemoryManager& gpu_memory, const Device& device,
|
||||
StateTracker& state_tracker_)
|
||||
: TextureCacheBase{rasterizer, maxwell3d, gpu_memory, device.HasASTC()}, state_tracker{
|
||||
state_tracker_} {
|
||||
src_framebuffer.Create();
|
||||
dst_framebuffer.Create();
|
||||
}
|
||||
|
||||
@@ -129,8 +129,10 @@ private:
|
||||
|
||||
class TextureCacheOpenGL final : public TextureCacheBase {
|
||||
public:
|
||||
explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
const Device& device, StateTracker& state_tracker);
|
||||
explicit TextureCacheOpenGL(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::MemoryManager& gpu_memory, const Device& device,
|
||||
StateTracker& state_tracker);
|
||||
~TextureCacheOpenGL();
|
||||
|
||||
protected:
|
||||
|
||||
@@ -275,11 +275,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
RendererOpenGL::RendererOpenGL(Core::System& system_, Core::Frontend::EmuWindow& emu_window_,
|
||||
Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
|
||||
: RendererBase{emu_window_, std::move(context_)}, system{system_},
|
||||
emu_window{emu_window_}, gpu{gpu_}, program_manager{device}, has_debug_tool{HasDebugTool()} {}
|
||||
RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
|
||||
Core::Frontend::EmuWindow& emu_window_,
|
||||
Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context)
|
||||
: RendererBase{emu_window_, std::move(context)}, telemetry_session{telemetry_session_},
|
||||
emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, program_manager{device},
|
||||
has_debug_tool{HasDebugTool()} {}
|
||||
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
|
||||
@@ -386,7 +388,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
|
||||
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format)};
|
||||
const u32 bytes_per_pixel{VideoCore::Surface::GetBytesPerPixel(pixel_format)};
|
||||
const u64 size_in_bytes{framebuffer.stride * framebuffer.height * bytes_per_pixel};
|
||||
u8* const host_ptr{system.Memory().GetPointer(framebuffer_addr)};
|
||||
u8* const host_ptr{cpu_memory.GetPointer(framebuffer_addr)};
|
||||
rasterizer->FlushRegion(ToCacheAddr(host_ptr), size_in_bytes);
|
||||
|
||||
// TODO(Rodrigo): Read this from HLE
|
||||
@@ -471,7 +473,6 @@ void RendererOpenGL::AddTelemetryFields() {
|
||||
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
|
||||
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
|
||||
|
||||
auto& telemetry_session = system.TelemetrySession();
|
||||
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(user_system, "GPU_Vendor", gpu_vendor);
|
||||
telemetry_session.AddField(user_system, "GPU_Model", gpu_model);
|
||||
@@ -482,8 +483,8 @@ void RendererOpenGL::CreateRasterizer() {
|
||||
if (rasterizer) {
|
||||
return;
|
||||
}
|
||||
rasterizer = std::make_unique<RasterizerOpenGL>(system, emu_window, device, screen_info,
|
||||
program_manager, state_tracker);
|
||||
rasterizer = std::make_unique<RasterizerOpenGL>(emu_window, gpu, cpu_memory, device,
|
||||
screen_info, program_manager, state_tracker);
|
||||
}
|
||||
|
||||
void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
|
||||
|
||||
@@ -16,16 +16,25 @@
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
class TelemetrySession;
|
||||
} // namespace Core
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class GPU;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
/// Structure used for storing information about the textures for the Switch screen
|
||||
@@ -56,7 +65,8 @@ class FrameMailbox;
|
||||
|
||||
class RendererOpenGL final : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window,
|
||||
explicit RendererOpenGL(Core::TelemetrySession& telemetry_session,
|
||||
Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
|
||||
Tegra::GPU& gpu,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context);
|
||||
~RendererOpenGL() override;
|
||||
@@ -94,12 +104,13 @@ private:
|
||||
|
||||
bool Present(int timeout_ms);
|
||||
|
||||
Core::System& system;
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
Core::Memory::Memory& cpu_memory;
|
||||
Tegra::GPU& gpu;
|
||||
const Device device;
|
||||
|
||||
StateTracker state_tracker{system};
|
||||
const Device device;
|
||||
StateTracker state_tracker{gpu};
|
||||
|
||||
// OpenGL object IDs
|
||||
OGLBuffer vertex_buffer;
|
||||
|
||||
@@ -86,7 +86,7 @@ Common::DynamicLibrary OpenVulkanLibrary() {
|
||||
if (!library.Open(filename.c_str())) {
|
||||
// Android devices may not have libvulkan.so.1, only libvulkan.so.
|
||||
filename = Common::DynamicLibrary::GetVersionedFilename("vulkan");
|
||||
library.Open(filename.c_str());
|
||||
(void)library.Open(filename.c_str());
|
||||
}
|
||||
#endif
|
||||
return library;
|
||||
@@ -237,10 +237,12 @@ std::string BuildCommaSeparatedExtensions(std::vector<std::string> available_ext
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
RendererVulkan::RendererVulkan(Core::System& system_, Core::Frontend::EmuWindow& emu_window,
|
||||
Tegra::GPU& gpu_,
|
||||
RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
|
||||
Core::Frontend::EmuWindow& emu_window,
|
||||
Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context)
|
||||
: RendererBase{emu_window, std::move(context)}, system{system_}, gpu{gpu_} {}
|
||||
: RendererBase{emu_window, std::move(context)}, telemetry_session{telemetry_session_},
|
||||
cpu_memory{cpu_memory_}, gpu{gpu_} {}
|
||||
|
||||
RendererVulkan::~RendererVulkan() {
|
||||
ShutDown();
|
||||
@@ -304,15 +306,15 @@ bool RendererVulkan::Init() {
|
||||
swapchain = std::make_unique<VKSwapchain>(*surface, *device);
|
||||
swapchain->Create(framebuffer.width, framebuffer.height, false);
|
||||
|
||||
state_tracker = std::make_unique<StateTracker>(system);
|
||||
state_tracker = std::make_unique<StateTracker>(gpu);
|
||||
|
||||
scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker);
|
||||
|
||||
rasterizer = std::make_unique<RasterizerVulkan>(system, render_window, screen_info, *device,
|
||||
*resource_manager, *memory_manager,
|
||||
*state_tracker, *scheduler);
|
||||
rasterizer = std::make_unique<RasterizerVulkan>(
|
||||
render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, *device,
|
||||
*resource_manager, *memory_manager, *state_tracker, *scheduler);
|
||||
|
||||
blit_screen = std::make_unique<VKBlitScreen>(system, render_window, *rasterizer, *device,
|
||||
blit_screen = std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device,
|
||||
*resource_manager, *memory_manager, *swapchain,
|
||||
*scheduler, screen_info);
|
||||
|
||||
@@ -440,8 +442,7 @@ void RendererVulkan::Report() const {
|
||||
LOG_INFO(Render_Vulkan, "Device: {}", model_name);
|
||||
LOG_INFO(Render_Vulkan, "Vulkan: {}", api_version);
|
||||
|
||||
auto& telemetry_session = system.TelemetrySession();
|
||||
constexpr auto field = Common::Telemetry::FieldType::UserSystem;
|
||||
static constexpr auto field = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(field, "GPU_Vendor", vendor_name);
|
||||
telemetry_session.AddField(field, "GPU_Model", model_name);
|
||||
telemetry_session.AddField(field, "GPU_Vulkan_Driver", driver_name);
|
||||
|
||||
@@ -14,7 +14,15 @@
|
||||
#include "video_core/renderer_vulkan/wrapper.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
class TelemetrySession;
|
||||
}
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class GPU;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
@@ -38,7 +46,8 @@ struct VKScreenInfo {
|
||||
|
||||
class RendererVulkan final : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererVulkan(Core::System& system, Core::Frontend::EmuWindow& emu_window,
|
||||
explicit RendererVulkan(Core::TelemetrySession& telemtry_session,
|
||||
Core::Frontend::EmuWindow& emu_window, Core::Memory::Memory& cpu_memory,
|
||||
Tegra::GPU& gpu,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context);
|
||||
~RendererVulkan() override;
|
||||
@@ -59,7 +68,8 @@ private:
|
||||
|
||||
void Report() const;
|
||||
|
||||
Core::System& system;
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
Core::Memory::Memory& cpu_memory;
|
||||
Tegra::GPU& gpu;
|
||||
|
||||
Common::DynamicLibrary library;
|
||||
|
||||
@@ -210,14 +210,16 @@ struct VKBlitScreen::BufferData {
|
||||
// Unaligned image data goes here
|
||||
};
|
||||
|
||||
VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
|
||||
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
|
||||
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
|
||||
VKSwapchain& swapchain, VKScheduler& scheduler,
|
||||
const VKScreenInfo& screen_info)
|
||||
: system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device},
|
||||
resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain},
|
||||
scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} {
|
||||
VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
|
||||
Core::Frontend::EmuWindow& render_window_,
|
||||
VideoCore::RasterizerInterface& rasterizer_, const VKDevice& device_,
|
||||
VKResourceManager& resource_manager_, VKMemoryManager& memory_manager_,
|
||||
VKSwapchain& swapchain_, VKScheduler& scheduler_,
|
||||
const VKScreenInfo& screen_info_)
|
||||
: cpu_memory{cpu_memory_}, render_window{render_window_},
|
||||
rasterizer{rasterizer_}, device{device_}, resource_manager{resource_manager_},
|
||||
memory_manager{memory_manager_}, swapchain{swapchain_}, scheduler{scheduler_},
|
||||
image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
|
||||
watches.resize(image_count);
|
||||
std::generate(watches.begin(), watches.end(),
|
||||
[]() { return std::make_unique<VKFenceWatch>(); });
|
||||
@@ -259,7 +261,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
|
||||
const auto pixel_format =
|
||||
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
|
||||
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
|
||||
const auto host_ptr = system.Memory().GetPointer(framebuffer_addr);
|
||||
const auto host_ptr = cpu_memory.GetPointer(framebuffer_addr);
|
||||
rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer));
|
||||
|
||||
// TODO(Rodrigo): Read this from HLE
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
@@ -39,7 +43,8 @@ class VKSwapchain;
|
||||
|
||||
class VKBlitScreen final {
|
||||
public:
|
||||
explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
|
||||
explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
|
||||
Core::Frontend::EmuWindow& render_window,
|
||||
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
|
||||
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
|
||||
VKSwapchain& swapchain, VKScheduler& scheduler,
|
||||
@@ -81,7 +86,7 @@ private:
|
||||
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
|
||||
std::size_t image_index) const;
|
||||
|
||||
Core::System& system;
|
||||
Core::Memory::Memory& cpu_memory;
|
||||
Core::Frontend::EmuWindow& render_window;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
const VKDevice& device;
|
||||
|
||||
@@ -145,14 +145,15 @@ void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst
|
||||
});
|
||||
}
|
||||
|
||||
VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
|
||||
const VKDevice& device, VKMemoryManager& memory_manager,
|
||||
VKScheduler& scheduler, VKStagingBufferPool& staging_pool)
|
||||
: VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, system,
|
||||
CreateStreamBuffer(device,
|
||||
scheduler)},
|
||||
device{device}, memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{
|
||||
staging_pool} {}
|
||||
VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
|
||||
const VKDevice& device_, VKMemoryManager& memory_manager_,
|
||||
VKScheduler& scheduler_, VKStagingBufferPool& staging_pool_)
|
||||
: VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer, gpu_memory, cpu_memory,
|
||||
CreateStreamBuffer(device_,
|
||||
scheduler_)},
|
||||
device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
|
||||
staging_pool_} {}
|
||||
|
||||
VKBufferCache::~VKBufferCache() = default;
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@
|
||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||
#include "video_core/renderer_vulkan/wrapper.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class VKDevice;
|
||||
@@ -53,7 +49,8 @@ private:
|
||||
|
||||
class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> {
|
||||
public:
|
||||
explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer, Core::System& system,
|
||||
explicit VKBufferCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
|
||||
const VKDevice& device, VKMemoryManager& memory_manager,
|
||||
VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
|
||||
~VKBufferCache();
|
||||
|
||||
@@ -71,12 +71,12 @@ bool InnerFence::IsEventSignalled() const {
|
||||
}
|
||||
}
|
||||
|
||||
VKFenceManager::VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
const VKDevice& device, VKScheduler& scheduler,
|
||||
VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
|
||||
VKQueryCache& query_cache)
|
||||
: GenericFenceManager(system, rasterizer, texture_cache, buffer_cache, query_cache),
|
||||
device{device}, scheduler{scheduler} {}
|
||||
VKFenceManager::VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
|
||||
Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
|
||||
VKBufferCache& buffer_cache, VKQueryCache& query_cache,
|
||||
const VKDevice& device_, VKScheduler& scheduler_)
|
||||
: GenericFenceManager(rasterizer, gpu, texture_cache, buffer_cache, query_cache),
|
||||
device{device_}, scheduler{scheduler_} {}
|
||||
|
||||
Fence VKFenceManager::CreateFence(u32 value, bool is_stubbed) {
|
||||
return std::make_shared<InnerFence>(device, scheduler, value, is_stubbed);
|
||||
|
||||
@@ -55,10 +55,10 @@ using GenericFenceManager =
|
||||
|
||||
class VKFenceManager final : public GenericFenceManager {
|
||||
public:
|
||||
explicit VKFenceManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
const VKDevice& device, VKScheduler& scheduler,
|
||||
VKTextureCache& texture_cache, VKBufferCache& buffer_cache,
|
||||
VKQueryCache& query_cache);
|
||||
explicit VKFenceManager(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
|
||||
Tegra::MemoryManager& memory_manager, VKTextureCache& texture_cache,
|
||||
VKBufferCache& buffer_cache, VKQueryCache& query_cache,
|
||||
const VKDevice& device, VKScheduler& scheduler);
|
||||
|
||||
protected:
|
||||
Fence CreateFence(u32 value, bool is_stubbed) override;
|
||||
|
||||
@@ -135,64 +135,56 @@ bool ComputePipelineCacheKey::operator==(const ComputePipelineCacheKey& rhs) con
|
||||
return std::memcmp(&rhs, this, sizeof *this) == 0;
|
||||
}
|
||||
|
||||
Shader::Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
|
||||
VideoCommon::Shader::ProgramCode program_code, u32 main_offset)
|
||||
: gpu_addr{gpu_addr}, program_code{std::move(program_code)},
|
||||
registry{stage, GetEngine(system, stage)}, shader_ir{this->program_code, main_offset,
|
||||
compiler_settings, registry},
|
||||
entries{GenerateShaderEntries(shader_ir)} {}
|
||||
Shader::Shader(Tegra::Engines::ConstBufferEngineInterface& engine, Tegra::Engines::ShaderType stage,
|
||||
GPUVAddr gpu_addr_, VAddr cpu_addr, VideoCommon::Shader::ProgramCode program_code_,
|
||||
u32 main_offset)
|
||||
: gpu_addr(gpu_addr_), program_code(std::move(program_code_)), registry(stage, engine),
|
||||
shader_ir(program_code, main_offset, compiler_settings, registry),
|
||||
entries(GenerateShaderEntries(shader_ir)) {}
|
||||
|
||||
Shader::~Shader() = default;
|
||||
|
||||
Tegra::Engines::ConstBufferEngineInterface& Shader::GetEngine(Core::System& system,
|
||||
Tegra::Engines::ShaderType stage) {
|
||||
if (stage == ShaderType::Compute) {
|
||||
return system.GPU().KeplerCompute();
|
||||
} else {
|
||||
return system.GPU().Maxwell3D();
|
||||
}
|
||||
}
|
||||
|
||||
VKPipelineCache::VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
|
||||
const VKDevice& device, VKScheduler& scheduler,
|
||||
VKDescriptorPool& descriptor_pool,
|
||||
VKUpdateDescriptorQueue& update_descriptor_queue,
|
||||
VKRenderPassCache& renderpass_cache)
|
||||
: VideoCommon::ShaderCache<Shader>{rasterizer}, system{system}, device{device},
|
||||
scheduler{scheduler}, descriptor_pool{descriptor_pool},
|
||||
update_descriptor_queue{update_descriptor_queue}, renderpass_cache{renderpass_cache} {}
|
||||
VKPipelineCache::VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu_,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
||||
Tegra::Engines::KeplerCompute& kepler_compute_,
|
||||
Tegra::MemoryManager& gpu_memory_, const VKDevice& device_,
|
||||
VKScheduler& scheduler_, VKDescriptorPool& descriptor_pool_,
|
||||
VKUpdateDescriptorQueue& update_descriptor_queue_,
|
||||
VKRenderPassCache& renderpass_cache_)
|
||||
: VideoCommon::ShaderCache<Shader>{rasterizer}, gpu{gpu_}, maxwell3d{maxwell3d_},
|
||||
kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, device{device_},
|
||||
scheduler{scheduler_}, descriptor_pool{descriptor_pool_},
|
||||
update_descriptor_queue{update_descriptor_queue_}, renderpass_cache{renderpass_cache_} {}
|
||||
|
||||
VKPipelineCache::~VKPipelineCache() = default;
|
||||
|
||||
std::array<Shader*, Maxwell::MaxShaderProgram> VKPipelineCache::GetShaders() {
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
|
||||
std::array<Shader*, Maxwell::MaxShaderProgram> shaders{};
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
|
||||
const auto program{static_cast<Maxwell::ShaderProgram>(index)};
|
||||
|
||||
// Skip stages that are not enabled
|
||||
if (!gpu.regs.IsShaderConfigEnabled(index)) {
|
||||
if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const GPUVAddr program_addr{GetShaderAddress(system, program)};
|
||||
const std::optional cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
|
||||
const GPUVAddr gpu_addr{GetShaderAddress(maxwell3d, program)};
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr);
|
||||
|
||||
Shader* result = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
|
||||
if (!result) {
|
||||
const auto host_ptr{memory_manager.GetPointer(program_addr)};
|
||||
const u8* const host_ptr{gpu_memory.GetPointer(gpu_addr)};
|
||||
|
||||
// No shader found - create a new one
|
||||
constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
|
||||
static constexpr u32 stage_offset = STAGE_MAIN_OFFSET;
|
||||
const auto stage = static_cast<ShaderType>(index == 0 ? 0 : index - 1);
|
||||
ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, false);
|
||||
ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, false);
|
||||
const std::size_t size_in_bytes = code.size() * sizeof(u64);
|
||||
|
||||
auto shader = std::make_unique<Shader>(system, stage, program_addr, std::move(code),
|
||||
stage_offset);
|
||||
auto shader = std::make_unique<Shader>(maxwell3d, stage, gpu_addr, *cpu_addr,
|
||||
std::move(code), stage_offset);
|
||||
result = shader.get();
|
||||
|
||||
if (cpu_addr) {
|
||||
@@ -215,11 +207,11 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
|
||||
}
|
||||
last_graphics_key = key;
|
||||
|
||||
if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(system.GPU())) {
|
||||
if (device.UseAsynchronousShaders() && async_shaders.IsShaderAsync(gpu)) {
|
||||
std::unique_lock lock{pipeline_cache};
|
||||
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
|
||||
if (is_cache_miss) {
|
||||
system.GPU().ShaderNotify().MarkSharderBuilding();
|
||||
gpu.ShaderNotify().MarkSharderBuilding();
|
||||
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
|
||||
const auto [program, bindings] = DecompileShaders(key.fixed_state);
|
||||
async_shaders.QueueVulkanShader(this, device, scheduler, descriptor_pool,
|
||||
@@ -233,13 +225,13 @@ VKGraphicsPipeline* VKPipelineCache::GetGraphicsPipeline(
|
||||
const auto [pair, is_cache_miss] = graphics_cache.try_emplace(key);
|
||||
auto& entry = pair->second;
|
||||
if (is_cache_miss) {
|
||||
system.GPU().ShaderNotify().MarkSharderBuilding();
|
||||
gpu.ShaderNotify().MarkSharderBuilding();
|
||||
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
|
||||
const auto [program, bindings] = DecompileShaders(key.fixed_state);
|
||||
entry = std::make_unique<VKGraphicsPipeline>(device, scheduler, descriptor_pool,
|
||||
update_descriptor_queue, renderpass_cache, key,
|
||||
bindings, program);
|
||||
system.GPU().ShaderNotify().MarkShaderComplete();
|
||||
gpu.ShaderNotify().MarkShaderComplete();
|
||||
}
|
||||
last_graphics_pipeline = entry.get();
|
||||
return last_graphics_pipeline;
|
||||
@@ -255,22 +247,21 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
|
||||
}
|
||||
LOG_INFO(Render_Vulkan, "Compile 0x{:016X}", key.Hash());
|
||||
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const auto program_addr = key.shader;
|
||||
const GPUVAddr gpu_addr = key.shader;
|
||||
|
||||
const auto cpu_addr = memory_manager.GpuToCpuAddress(program_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr);
|
||||
|
||||
Shader* shader = cpu_addr ? TryGet(*cpu_addr) : null_kernel.get();
|
||||
if (!shader) {
|
||||
// No shader found - create a new one
|
||||
const auto host_ptr = memory_manager.GetPointer(program_addr);
|
||||
const auto host_ptr = gpu_memory.GetPointer(gpu_addr);
|
||||
|
||||
ProgramCode code = GetShaderCode(memory_manager, program_addr, host_ptr, true);
|
||||
ProgramCode code = GetShaderCode(gpu_memory, gpu_addr, host_ptr, true);
|
||||
const std::size_t size_in_bytes = code.size() * sizeof(u64);
|
||||
|
||||
auto shader_info = std::make_unique<Shader>(system, ShaderType::Compute, program_addr,
|
||||
std::move(code), KERNEL_MAIN_OFFSET);
|
||||
auto shader_info = std::make_unique<Shader>(kepler_compute, ShaderType::Compute, gpu_addr,
|
||||
*cpu_addr, std::move(code), KERNEL_MAIN_OFFSET);
|
||||
shader = shader_info.get();
|
||||
|
||||
if (cpu_addr) {
|
||||
@@ -298,7 +289,7 @@ VKComputePipeline& VKPipelineCache::GetComputePipeline(const ComputePipelineCach
|
||||
}
|
||||
|
||||
void VKPipelineCache::EmplacePipeline(std::unique_ptr<VKGraphicsPipeline> pipeline) {
|
||||
system.GPU().ShaderNotify().MarkShaderComplete();
|
||||
gpu.ShaderNotify().MarkShaderComplete();
|
||||
std::unique_lock lock{pipeline_cache};
|
||||
graphics_cache.at(pipeline->GetCacheKey()) = std::move(pipeline);
|
||||
}
|
||||
@@ -339,9 +330,6 @@ void VKPipelineCache::OnShaderRemoval(Shader* shader) {
|
||||
|
||||
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>>
|
||||
VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
|
||||
auto& memory_manager = system.GPU().MemoryManager();
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
|
||||
Specialization specialization;
|
||||
if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
|
||||
device.IsExtExtendedDynamicStateSupported()) {
|
||||
@@ -364,12 +352,12 @@ VKPipelineCache::DecompileShaders(const FixedPipelineState& fixed_state) {
|
||||
const auto program_enum = static_cast<Maxwell::ShaderProgram>(index);
|
||||
|
||||
// Skip stages that are not enabled
|
||||
if (!gpu.regs.IsShaderConfigEnabled(index)) {
|
||||
if (!maxwell3d.regs.IsShaderConfigEnabled(index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GPUVAddr gpu_addr = GetShaderAddress(system, program_enum);
|
||||
const std::optional<VAddr> cpu_addr = memory_manager.GpuToCpuAddress(gpu_addr);
|
||||
const GPUVAddr gpu_addr = GetShaderAddress(maxwell3d, program_enum);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
Shader* const shader = cpu_addr ? TryGet(*cpu_addr) : null_shader.get();
|
||||
|
||||
const std::size_t stage = index == 0 ? 0 : index - 1; // Stage indices are 0 - 5
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace Vulkan {
|
||||
|
||||
class Shader {
|
||||
public:
|
||||
explicit Shader(Core::System& system, Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr,
|
||||
explicit Shader(Tegra::Engines::ConstBufferEngineInterface& engine,
|
||||
Tegra::Engines::ShaderType stage, GPUVAddr gpu_addr, VAddr cpu_addr,
|
||||
VideoCommon::Shader::ProgramCode program_code, u32 main_offset);
|
||||
~Shader();
|
||||
|
||||
@@ -97,22 +98,19 @@ public:
|
||||
return shader_ir;
|
||||
}
|
||||
|
||||
const VideoCommon::Shader::Registry& GetRegistry() const {
|
||||
return registry;
|
||||
}
|
||||
|
||||
const VideoCommon::Shader::ShaderIR& GetIR() const {
|
||||
return shader_ir;
|
||||
}
|
||||
|
||||
const VideoCommon::Shader::Registry& GetRegistry() const {
|
||||
return registry;
|
||||
}
|
||||
|
||||
const ShaderEntries& GetEntries() const {
|
||||
return entries;
|
||||
}
|
||||
|
||||
private:
|
||||
static Tegra::Engines::ConstBufferEngineInterface& GetEngine(Core::System& system,
|
||||
Tegra::Engines::ShaderType stage);
|
||||
|
||||
GPUVAddr gpu_addr{};
|
||||
VideoCommon::Shader::ProgramCode program_code;
|
||||
VideoCommon::Shader::Registry registry;
|
||||
@@ -122,9 +120,11 @@ private:
|
||||
|
||||
class VKPipelineCache final : public VideoCommon::ShaderCache<Shader> {
|
||||
public:
|
||||
explicit VKPipelineCache(Core::System& system, RasterizerVulkan& rasterizer,
|
||||
const VKDevice& device, VKScheduler& scheduler,
|
||||
VKDescriptorPool& descriptor_pool,
|
||||
explicit VKPipelineCache(RasterizerVulkan& rasterizer, Tegra::GPU& gpu,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
Tegra::MemoryManager& gpu_memory, const VKDevice& device,
|
||||
VKScheduler& scheduler, VKDescriptorPool& descriptor_pool,
|
||||
VKUpdateDescriptorQueue& update_descriptor_queue,
|
||||
VKRenderPassCache& renderpass_cache);
|
||||
~VKPipelineCache() override;
|
||||
@@ -145,7 +145,11 @@ private:
|
||||
std::pair<SPIRVProgram, std::vector<VkDescriptorSetLayoutBinding>> DecompileShaders(
|
||||
const FixedPipelineState& fixed_state);
|
||||
|
||||
Core::System& system;
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
const VKDevice& device;
|
||||
VKScheduler& scheduler;
|
||||
VKDescriptorPool& descriptor_pool;
|
||||
|
||||
@@ -68,10 +68,11 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
|
||||
usage[pool_index * GROW_STEP + static_cast<std::ptrdiff_t>(query.second)] = false;
|
||||
}
|
||||
|
||||
VKQueryCache::VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
|
||||
const VKDevice& device, VKScheduler& scheduler)
|
||||
: VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
|
||||
QueryPool>{system, rasterizer},
|
||||
QueryPool>{rasterizer, maxwell3d, gpu_memory},
|
||||
device{device}, scheduler{scheduler} {
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
|
||||
query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
|
||||
|
||||
@@ -56,7 +56,8 @@ class VKQueryCache final
|
||||
: public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
|
||||
QueryPool> {
|
||||
public:
|
||||
explicit VKQueryCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
|
||||
const VKDevice& device, VKScheduler& scheduler);
|
||||
~VKQueryCache();
|
||||
|
||||
|
||||
@@ -381,28 +381,30 @@ void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const {
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerVulkan::RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& renderer,
|
||||
VKScreenInfo& screen_info, const VKDevice& device,
|
||||
VKResourceManager& resource_manager,
|
||||
VKMemoryManager& memory_manager, StateTracker& state_tracker,
|
||||
VKScheduler& scheduler)
|
||||
: RasterizerAccelerated{system.Memory()}, system{system}, render_window{renderer},
|
||||
screen_info{screen_info}, device{device}, resource_manager{resource_manager},
|
||||
memory_manager{memory_manager}, state_tracker{state_tracker}, scheduler{scheduler},
|
||||
RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
|
||||
Tegra::MemoryManager& gpu_memory_,
|
||||
Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info_,
|
||||
const VKDevice& device_, VKResourceManager& resource_manager_,
|
||||
VKMemoryManager& memory_manager_, StateTracker& state_tracker_,
|
||||
VKScheduler& scheduler_)
|
||||
: RasterizerAccelerated(cpu_memory), gpu(gpu_), gpu_memory(gpu_memory_),
|
||||
maxwell3d(gpu.Maxwell3D()), kepler_compute(gpu.KeplerCompute()), screen_info(screen_info_),
|
||||
device(device_), resource_manager(resource_manager_), memory_manager(memory_manager_),
|
||||
state_tracker(state_tracker_), scheduler(scheduler_),
|
||||
staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
|
||||
update_descriptor_queue(device, scheduler), renderpass_cache(device),
|
||||
quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
|
||||
quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
|
||||
uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
|
||||
texture_cache(system, *this, device, resource_manager, memory_manager, scheduler,
|
||||
staging_pool),
|
||||
pipeline_cache(system, *this, device, scheduler, descriptor_pool, update_descriptor_queue,
|
||||
renderpass_cache),
|
||||
buffer_cache(*this, system, device, memory_manager, scheduler, staging_pool),
|
||||
sampler_cache(device),
|
||||
fence_manager(system, *this, device, scheduler, texture_cache, buffer_cache, query_cache),
|
||||
query_cache(system, *this, device, scheduler),
|
||||
wfi_event{device.GetLogical().CreateNewEvent()}, async_shaders{renderer} {
|
||||
texture_cache(*this, maxwell3d, gpu_memory, device, resource_manager, memory_manager,
|
||||
scheduler, staging_pool),
|
||||
pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
|
||||
descriptor_pool, update_descriptor_queue, renderpass_cache),
|
||||
buffer_cache(*this, gpu_memory, cpu_memory, device, memory_manager, scheduler, staging_pool),
|
||||
sampler_cache(device), query_cache(*this, maxwell3d, gpu_memory, device, scheduler),
|
||||
fence_manager(*this, gpu, gpu_memory, texture_cache, buffer_cache, query_cache, device,
|
||||
scheduler),
|
||||
wfi_event(device.GetLogical().CreateNewEvent()), async_shaders(emu_window) {
|
||||
scheduler.SetQueryCache(query_cache);
|
||||
if (device.UseAsynchronousShaders()) {
|
||||
async_shaders.AllocateWorkers();
|
||||
@@ -414,15 +416,13 @@ RasterizerVulkan::~RasterizerVulkan() = default;
|
||||
void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Drawing);
|
||||
|
||||
SCOPE_EXIT({ gpu.TickWork(); });
|
||||
FlushWork();
|
||||
|
||||
query_cache.UpdateCounters();
|
||||
|
||||
SCOPE_EXIT({ system.GPU().TickWork(); });
|
||||
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
GraphicsPipelineCacheKey key;
|
||||
key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
|
||||
key.fixed_state.Fill(maxwell3d.regs, device.IsExtExtendedDynamicStateSupported());
|
||||
|
||||
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
|
||||
|
||||
@@ -480,8 +480,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
|
||||
void RasterizerVulkan::Clear() {
|
||||
MICROPROFILE_SCOPE(Vulkan_Clearing);
|
||||
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
if (!system.GPU().Maxwell3D().ShouldExecute()) {
|
||||
if (!maxwell3d.ShouldExecute()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -490,7 +489,7 @@ void RasterizerVulkan::Clear() {
|
||||
|
||||
query_cache.UpdateCounters();
|
||||
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
const bool use_color = regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B ||
|
||||
regs.clear_buffers.A;
|
||||
const bool use_depth = regs.clear_buffers.Z;
|
||||
@@ -559,7 +558,7 @@ void RasterizerVulkan::DispatchCompute(GPUVAddr code_addr) {
|
||||
|
||||
query_cache.UpdateCounters();
|
||||
|
||||
const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
|
||||
const auto& launch_desc = kepler_compute.launch_description;
|
||||
auto& pipeline = pipeline_cache.GetComputePipeline({
|
||||
.shader = code_addr,
|
||||
.shared_memory_size = launch_desc.shared_alloc,
|
||||
@@ -655,16 +654,14 @@ void RasterizerVulkan::SyncGuestHost() {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SignalSemaphore(GPUVAddr addr, u32 value) {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
gpu.MemoryManager().Write<u32>(addr, value);
|
||||
gpu_memory.Write<u32>(addr, value);
|
||||
return;
|
||||
}
|
||||
fence_manager.SignalSemaphore(addr, value);
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SignalSyncPoint(u32 value) {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
gpu.IncrementSyncPoint(value);
|
||||
return;
|
||||
@@ -673,7 +670,6 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::ReleaseFences() {
|
||||
auto& gpu{system.GPU()};
|
||||
if (!gpu.IsAsync()) {
|
||||
return;
|
||||
}
|
||||
@@ -751,10 +747,6 @@ bool RasterizerVulkan::AccelerateDisplay(const Tegra::FramebufferConfig& config,
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupDirtyFlags() {
|
||||
state_tracker.Initialize();
|
||||
}
|
||||
|
||||
void RasterizerVulkan::FlushWork() {
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
|
||||
|
||||
@@ -778,10 +770,9 @@ void RasterizerVulkan::FlushWork() {
|
||||
|
||||
RasterizerVulkan::Texceptions RasterizerVulkan::UpdateAttachments(bool is_clear) {
|
||||
MICROPROFILE_SCOPE(Vulkan_RenderTargets);
|
||||
auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
auto& dirty = maxwell3d.dirty.flags;
|
||||
auto& regs = maxwell3d.regs;
|
||||
|
||||
const auto& regs = maxwell3d.regs;
|
||||
auto& dirty = maxwell3d.dirty.flags;
|
||||
const bool update_rendertargets = dirty[VideoCommon::Dirty::RenderTargets];
|
||||
dirty[VideoCommon::Dirty::RenderTargets] = false;
|
||||
|
||||
@@ -844,7 +835,7 @@ std::tuple<VkFramebuffer, VkExtent2D> RasterizerVulkan::ConfigureFramebuffers(
|
||||
return true;
|
||||
};
|
||||
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
|
||||
for (std::size_t index = 0; index < num_attachments; ++index) {
|
||||
if (try_push(color_attachments[index])) {
|
||||
@@ -880,13 +871,12 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
|
||||
bool is_instanced) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Geometry);
|
||||
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
SetupVertexArrays(buffer_bindings);
|
||||
|
||||
const u32 base_instance = regs.vb_base_instance;
|
||||
const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
|
||||
const u32 num_instances = is_instanced ? maxwell3d.mme_draw.instance_count : 1;
|
||||
const u32 base_vertex = is_indexed ? regs.vb_element_base : regs.vertex_buffer.first;
|
||||
const u32 num_vertices = is_indexed ? regs.index_array.count : regs.vertex_buffer.count;
|
||||
|
||||
@@ -947,7 +937,7 @@ void RasterizerVulkan::SetupImageTransitions(
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UpdateDynamicStates() {
|
||||
auto& regs = system.GPU().Maxwell3D().regs;
|
||||
auto& regs = maxwell3d.regs;
|
||||
UpdateViewportsState(regs);
|
||||
UpdateScissorsState(regs);
|
||||
UpdateDepthBias(regs);
|
||||
@@ -968,7 +958,7 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::BeginTransformFeedback() {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.tfb_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1000,7 +990,7 @@ void RasterizerVulkan::BeginTransformFeedback() {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::EndTransformFeedback() {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
if (regs.tfb_enabled == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1013,7 +1003,7 @@ void RasterizerVulkan::EndTransformFeedback() {
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
const auto& vertex_array = regs.vertex_array[index];
|
||||
@@ -1039,7 +1029,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar
|
||||
if (params.num_vertices == 0) {
|
||||
return;
|
||||
}
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
switch (regs.draw.topology) {
|
||||
case Maxwell::PrimitiveTopology::Quads: {
|
||||
if (!params.is_indexed) {
|
||||
@@ -1087,8 +1077,7 @@ void RasterizerVulkan::SetupIndexBuffer(BufferBindings& buffer_bindings, DrawPar
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& shader_stage = gpu.state.shader_stages[stage];
|
||||
const auto& shader_stage = maxwell3d.state.shader_stages[stage];
|
||||
for (const auto& entry : entries.const_buffers) {
|
||||
SetupConstBuffer(entry, shader_stage.const_buffers[entry.GetIndex()]);
|
||||
}
|
||||
@@ -1096,8 +1085,7 @@ void RasterizerVulkan::SetupGraphicsConstBuffers(const ShaderEntries& entries, s
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
|
||||
auto& gpu{system.GPU()};
|
||||
const auto cbufs{gpu.Maxwell3D().state.shader_stages[stage]};
|
||||
const auto& cbufs{maxwell3d.state.shader_stages[stage]};
|
||||
|
||||
for (const auto& entry : entries.global_buffers) {
|
||||
const auto addr = cbufs.const_buffers[entry.GetCbufIndex()].address + entry.GetCbufOffset();
|
||||
@@ -1107,19 +1095,17 @@ void RasterizerVulkan::SetupGraphicsGlobalBuffers(const ShaderEntries& entries,
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsUniformTexels(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
for (const auto& entry : entries.uniform_texels) {
|
||||
const auto image = GetTextureInfo(gpu, entry, stage).tic;
|
||||
const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
|
||||
SetupUniformTexels(image, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
for (const auto& entry : entries.samplers) {
|
||||
for (std::size_t i = 0; i < entry.size; ++i) {
|
||||
const auto texture = GetTextureInfo(gpu, entry, stage, i);
|
||||
const auto texture = GetTextureInfo(maxwell3d, entry, stage, i);
|
||||
SetupTexture(texture, entry);
|
||||
}
|
||||
}
|
||||
@@ -1127,25 +1113,23 @@ void RasterizerVulkan::SetupGraphicsTextures(const ShaderEntries& entries, std::
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsStorageTexels(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
for (const auto& entry : entries.storage_texels) {
|
||||
const auto image = GetTextureInfo(gpu, entry, stage).tic;
|
||||
const auto image = GetTextureInfo(maxwell3d, entry, stage).tic;
|
||||
SetupStorageTexel(image, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupGraphicsImages(const ShaderEntries& entries, std::size_t stage) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Images);
|
||||
const auto& gpu = system.GPU().Maxwell3D();
|
||||
for (const auto& entry : entries.images) {
|
||||
const auto tic = GetTextureInfo(gpu, entry, stage).tic;
|
||||
const auto tic = GetTextureInfo(maxwell3d, entry, stage).tic;
|
||||
SetupImage(tic, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_ConstBuffers);
|
||||
const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
|
||||
const auto& launch_desc = kepler_compute.launch_description;
|
||||
for (const auto& entry : entries.const_buffers) {
|
||||
const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
|
||||
const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
|
||||
@@ -1159,7 +1143,7 @@ void RasterizerVulkan::SetupComputeConstBuffers(const ShaderEntries& entries) {
|
||||
|
||||
void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_GlobalBuffers);
|
||||
const auto cbufs{system.GPU().KeplerCompute().launch_description.const_buffer_config};
|
||||
const auto& cbufs{kepler_compute.launch_description.const_buffer_config};
|
||||
for (const auto& entry : entries.global_buffers) {
|
||||
const auto addr{cbufs[entry.GetCbufIndex()].Address() + entry.GetCbufOffset()};
|
||||
SetupGlobalBuffer(entry, addr);
|
||||
@@ -1168,19 +1152,17 @@ void RasterizerVulkan::SetupComputeGlobalBuffers(const ShaderEntries& entries) {
|
||||
|
||||
void RasterizerVulkan::SetupComputeUniformTexels(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().KeplerCompute();
|
||||
for (const auto& entry : entries.uniform_texels) {
|
||||
const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
|
||||
const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
|
||||
SetupUniformTexels(image, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().KeplerCompute();
|
||||
for (const auto& entry : entries.samplers) {
|
||||
for (std::size_t i = 0; i < entry.size; ++i) {
|
||||
const auto texture = GetTextureInfo(gpu, entry, ComputeShaderIndex, i);
|
||||
const auto texture = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex, i);
|
||||
SetupTexture(texture, entry);
|
||||
}
|
||||
}
|
||||
@@ -1188,18 +1170,16 @@ void RasterizerVulkan::SetupComputeTextures(const ShaderEntries& entries) {
|
||||
|
||||
void RasterizerVulkan::SetupComputeStorageTexels(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Textures);
|
||||
const auto& gpu = system.GPU().KeplerCompute();
|
||||
for (const auto& entry : entries.storage_texels) {
|
||||
const auto image = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
|
||||
const auto image = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
|
||||
SetupStorageTexel(image, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupComputeImages(const ShaderEntries& entries) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Images);
|
||||
const auto& gpu = system.GPU().KeplerCompute();
|
||||
for (const auto& entry : entries.images) {
|
||||
const auto tic = GetTextureInfo(gpu, entry, ComputeShaderIndex).tic;
|
||||
const auto tic = GetTextureInfo(kepler_compute, entry, ComputeShaderIndex).tic;
|
||||
SetupImage(tic, entry);
|
||||
}
|
||||
}
|
||||
@@ -1223,9 +1203,8 @@ void RasterizerVulkan::SetupConstBuffer(const ConstBufferEntry& entry,
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SetupGlobalBuffer(const GlobalBufferEntry& entry, GPUVAddr address) {
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto actual_addr = memory_manager.Read<u64>(address);
|
||||
const auto size = memory_manager.Read<u32>(address + 8);
|
||||
const u64 actual_addr = gpu_memory.Read<u64>(address);
|
||||
const u32 size = gpu_memory.Read<u32>(address + 8);
|
||||
|
||||
if (size == 0) {
|
||||
// Sometimes global memory pointers don't have a proper size. Upload a dummy entry
|
||||
@@ -1508,7 +1487,7 @@ std::size_t RasterizerVulkan::CalculateComputeStreamBufferSize() const {
|
||||
}
|
||||
|
||||
std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
|
||||
std::size_t size = 0;
|
||||
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||
@@ -1523,9 +1502,8 @@ std::size_t RasterizerVulkan::CalculateVertexArraysSize() const {
|
||||
}
|
||||
|
||||
std::size_t RasterizerVulkan::CalculateIndexBufferSize() const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
return static_cast<std::size_t>(regs.index_array.count) *
|
||||
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
|
||||
return static_cast<std::size_t>(maxwell3d.regs.index_array.count) *
|
||||
static_cast<std::size_t>(maxwell3d.regs.index_array.FormatSizeInBytes());
|
||||
}
|
||||
|
||||
std::size_t RasterizerVulkan::CalculateConstBufferSize(
|
||||
@@ -1540,7 +1518,7 @@ std::size_t RasterizerVulkan::CalculateConstBufferSize(
|
||||
}
|
||||
|
||||
RenderPassParams RasterizerVulkan::GetRenderPassParams(Texceptions texceptions) const {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
const auto& regs = maxwell3d.regs;
|
||||
const std::size_t num_attachments = static_cast<std::size_t>(regs.rt_control.count);
|
||||
|
||||
RenderPassParams params;
|
||||
|
||||
@@ -106,7 +106,8 @@ struct ImageView {
|
||||
|
||||
class RasterizerVulkan final : public VideoCore::RasterizerAccelerated {
|
||||
public:
|
||||
explicit RasterizerVulkan(Core::System& system, Core::Frontend::EmuWindow& render_window,
|
||||
explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
|
||||
Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
|
||||
VKScreenInfo& screen_info, const VKDevice& device,
|
||||
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
|
||||
StateTracker& state_tracker, VKScheduler& scheduler);
|
||||
@@ -135,7 +136,6 @@ public:
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) override;
|
||||
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
|
||||
u32 pixel_stride) override;
|
||||
void SetupDirtyFlags() override;
|
||||
|
||||
VideoCommon::Shader::AsyncShaders& GetAsyncShaders() {
|
||||
return async_shaders;
|
||||
@@ -279,8 +279,11 @@ private:
|
||||
|
||||
VkBuffer DefaultBuffer();
|
||||
|
||||
Core::System& system;
|
||||
Core::Frontend::EmuWindow& render_window;
|
||||
Tegra::GPU& gpu;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||
|
||||
VKScreenInfo& screen_info;
|
||||
const VKDevice& device;
|
||||
VKResourceManager& resource_manager;
|
||||
@@ -300,8 +303,8 @@ private:
|
||||
VKPipelineCache pipeline_cache;
|
||||
VKBufferCache buffer_cache;
|
||||
VKSamplerCache sampler_cache;
|
||||
VKFenceManager fence_manager;
|
||||
VKQueryCache query_cache;
|
||||
VKFenceManager fence_manager;
|
||||
|
||||
vk::Buffer default_buffer;
|
||||
VKMemoryCommit default_buffer_commit;
|
||||
|
||||
@@ -132,12 +132,9 @@ void SetupDirtyStencilTestEnable(Tables& tables) {
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
StateTracker::StateTracker(Core::System& system)
|
||||
: system{system}, invalidation_flags{MakeInvalidationFlags()} {}
|
||||
|
||||
void StateTracker::Initialize() {
|
||||
auto& dirty = system.GPU().Maxwell3D().dirty;
|
||||
auto& tables = dirty.tables;
|
||||
StateTracker::StateTracker(Tegra::GPU& gpu)
|
||||
: flags{gpu.Maxwell3D().dirty.flags}, invalidation_flags{MakeInvalidationFlags()} {
|
||||
auto& tables = gpu.Maxwell3D().dirty.tables;
|
||||
SetupDirtyRenderTargets(tables);
|
||||
SetupDirtyViewports(tables);
|
||||
SetupDirtyScissors(tables);
|
||||
@@ -155,9 +152,4 @@ void StateTracker::Initialize() {
|
||||
SetupDirtyStencilTestEnable(tables);
|
||||
}
|
||||
|
||||
void StateTracker::InvalidateCommandBufferState() {
|
||||
system.GPU().Maxwell3D().dirty.flags |= invalidation_flags;
|
||||
current_topology = INVALID_TOPOLOGY;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -45,11 +45,12 @@ class StateTracker {
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
|
||||
public:
|
||||
explicit StateTracker(Core::System& system);
|
||||
explicit StateTracker(Tegra::GPU& gpu);
|
||||
|
||||
void Initialize();
|
||||
|
||||
void InvalidateCommandBufferState();
|
||||
void InvalidateCommandBufferState() {
|
||||
flags |= invalidation_flags;
|
||||
current_topology = INVALID_TOPOLOGY;
|
||||
}
|
||||
|
||||
bool TouchViewports() {
|
||||
return Exchange(Dirty::Viewports, false);
|
||||
@@ -121,13 +122,12 @@ private:
|
||||
static constexpr auto INVALID_TOPOLOGY = static_cast<Maxwell::PrimitiveTopology>(~0u);
|
||||
|
||||
bool Exchange(std::size_t id, bool new_value) const noexcept {
|
||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||
const bool is_dirty = flags[id];
|
||||
flags[id] = new_value;
|
||||
return is_dirty;
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
Tegra::Engines::Maxwell3D::DirtyState::Flags& flags;
|
||||
Tegra::Engines::Maxwell3D::DirtyState::Flags invalidation_flags;
|
||||
Maxwell::PrimitiveTopology current_topology = INVALID_TOPOLOGY;
|
||||
};
|
||||
|
||||
@@ -57,9 +57,9 @@ u32 GetMemoryType(const VkPhysicalDeviceMemoryProperties& properties,
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
VKStreamBuffer::VKStreamBuffer(const VKDevice& device, VKScheduler& scheduler,
|
||||
VKStreamBuffer::VKStreamBuffer(const VKDevice& device_, VKScheduler& scheduler_,
|
||||
VkBufferUsageFlags usage)
|
||||
: device{device}, scheduler{scheduler} {
|
||||
: device{device_}, scheduler{scheduler_} {
|
||||
CreateBuffers(usage);
|
||||
ReserveWatches(current_watches, WATCHES_INITIAL_RESERVE);
|
||||
ReserveWatches(previous_watches, WATCHES_INITIAL_RESERVE);
|
||||
|
||||
@@ -188,13 +188,13 @@ u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::Swizzl
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
CachedSurface::CachedSurface(Core::System& system, const VKDevice& device,
|
||||
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
|
||||
VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
|
||||
GPUVAddr gpu_addr, const SurfaceParams& params)
|
||||
: SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, system{system},
|
||||
device{device}, resource_manager{resource_manager},
|
||||
memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {
|
||||
CachedSurface::CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
|
||||
VKMemoryManager& memory_manager, VKScheduler& scheduler,
|
||||
VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
|
||||
const SurfaceParams& params)
|
||||
: SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, device{device},
|
||||
resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
|
||||
staging_pool{staging_pool} {
|
||||
if (params.IsBuffer()) {
|
||||
buffer = CreateBuffer(device, params, host_memory_size);
|
||||
commit = memory_manager.Commit(buffer, false);
|
||||
@@ -490,19 +490,21 @@ VkImageView CachedSurfaceView::GetAttachment() {
|
||||
return *render_target;
|
||||
}
|
||||
|
||||
VKTextureCache::VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
const VKDevice& device, VKResourceManager& resource_manager,
|
||||
VKMemoryManager& memory_manager, VKScheduler& scheduler,
|
||||
VKStagingBufferPool& staging_pool)
|
||||
: TextureCache(system, rasterizer, device.IsOptimalAstcSupported()), device{device},
|
||||
resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
|
||||
staging_pool{staging_pool} {}
|
||||
VKTextureCache::VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::MemoryManager& gpu_memory, const VKDevice& device_,
|
||||
VKResourceManager& resource_manager_,
|
||||
VKMemoryManager& memory_manager_, VKScheduler& scheduler_,
|
||||
VKStagingBufferPool& staging_pool_)
|
||||
: TextureCache(rasterizer, maxwell3d, gpu_memory, device_.IsOptimalAstcSupported()),
|
||||
device{device_}, resource_manager{resource_manager_},
|
||||
memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{staging_pool_} {}
|
||||
|
||||
VKTextureCache::~VKTextureCache() = default;
|
||||
|
||||
Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
|
||||
return std::make_shared<CachedSurface>(system, device, resource_manager, memory_manager,
|
||||
scheduler, staging_pool, gpu_addr, params);
|
||||
return std::make_shared<CachedSurface>(device, resource_manager, memory_manager, scheduler,
|
||||
staging_pool, gpu_addr, params);
|
||||
}
|
||||
|
||||
void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
#include "video_core/texture_cache/surface_base.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
@@ -45,10 +41,10 @@ class CachedSurface final : public VideoCommon::SurfaceBase<View> {
|
||||
friend CachedSurfaceView;
|
||||
|
||||
public:
|
||||
explicit CachedSurface(Core::System& system, const VKDevice& device,
|
||||
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
|
||||
VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
|
||||
GPUVAddr gpu_addr, const SurfaceParams& params);
|
||||
explicit CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
|
||||
VKMemoryManager& memory_manager, VKScheduler& scheduler,
|
||||
VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
|
||||
const SurfaceParams& params);
|
||||
~CachedSurface();
|
||||
|
||||
void UploadTexture(const std::vector<u8>& staging_buffer) override;
|
||||
@@ -101,7 +97,6 @@ private:
|
||||
|
||||
VkImageSubresourceRange GetImageSubresourceRange() const;
|
||||
|
||||
Core::System& system;
|
||||
const VKDevice& device;
|
||||
VKResourceManager& resource_manager;
|
||||
VKMemoryManager& memory_manager;
|
||||
@@ -201,7 +196,8 @@ private:
|
||||
|
||||
class VKTextureCache final : public TextureCacheBase {
|
||||
public:
|
||||
explicit VKTextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
explicit VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
|
||||
const VKDevice& device, VKResourceManager& resource_manager,
|
||||
VKMemoryManager& memory_manager, VKScheduler& scheduler,
|
||||
VKStagingBufferPool& staging_pool);
|
||||
|
||||
@@ -16,11 +16,10 @@
|
||||
|
||||
namespace VideoCommon::Shader {
|
||||
|
||||
GPUVAddr GetShaderAddress(Core::System& system,
|
||||
GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program) {
|
||||
const auto& gpu{system.GPU().Maxwell3D()};
|
||||
const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
|
||||
return gpu.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
const auto& shader_config{maxwell3d.regs.shader_config[static_cast<std::size_t>(program)]};
|
||||
return maxwell3d.regs.code_address.CodeAddress() + shader_config.offset;
|
||||
}
|
||||
|
||||
bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/engines/shader_type.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
@@ -27,7 +23,7 @@ constexpr u32 STAGE_MAIN_OFFSET = 10;
|
||||
constexpr u32 KERNEL_MAIN_OFFSET = 0;
|
||||
|
||||
/// Gets the address for the specified shader stage program
|
||||
GPUVAddr GetShaderAddress(Core::System& system,
|
||||
GPUVAddr GetShaderAddress(Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
Tegra::Engines::Maxwell3D::Regs::ShaderProgram program);
|
||||
|
||||
/// Gets if the current instruction offset is a scheduler instruction
|
||||
|
||||
@@ -163,13 +163,11 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) {
|
||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d) {
|
||||
const auto& regs = maxwell3d.regs;
|
||||
const auto block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U);
|
||||
const bool is_layered = regs.zeta_layers > 1 && block_depth == 0;
|
||||
const auto pixel_format = PixelFormatFromDepthFormat(regs.zeta.format);
|
||||
|
||||
return {
|
||||
.is_tiled = regs.zeta.memory_layout.type ==
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear,
|
||||
@@ -191,8 +189,9 @@ SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) {
|
||||
};
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
|
||||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
|
||||
SurfaceParams SurfaceParams::CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
std::size_t index) {
|
||||
const auto& config{maxwell3d.regs.rt[index]};
|
||||
SurfaceParams params;
|
||||
params.is_tiled =
|
||||
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
|
||||
@@ -33,10 +33,11 @@ public:
|
||||
const VideoCommon::Shader::Image& entry);
|
||||
|
||||
/// Creates SurfaceCachedParams for a depth buffer configuration.
|
||||
static SurfaceParams CreateForDepthBuffer(Core::System& system);
|
||||
static SurfaceParams CreateForDepthBuffer(Tegra::Engines::Maxwell3D& maxwell3d);
|
||||
|
||||
/// Creates SurfaceCachedParams from a framebuffer configuration.
|
||||
static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
|
||||
static SurfaceParams CreateForFramebuffer(Tegra::Engines::Maxwell3D& maxwell3d,
|
||||
std::size_t index);
|
||||
|
||||
/// Creates SurfaceCachedParams from a Fermi2D surface configuration.
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
|
||||
@@ -135,8 +135,7 @@ public:
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
@@ -160,8 +159,7 @@ public:
|
||||
if (!gpu_addr) {
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
@@ -183,11 +181,11 @@ public:
|
||||
|
||||
TView GetDepthBufferSurface(bool preserve_contents) {
|
||||
std::lock_guard lock{mutex};
|
||||
auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
|
||||
auto& dirty = maxwell3d.dirty;
|
||||
if (!dirty.flags[VideoCommon::Dirty::ZetaBuffer]) {
|
||||
return depth_buffer.view;
|
||||
}
|
||||
maxwell3d.dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
|
||||
dirty.flags[VideoCommon::Dirty::ZetaBuffer] = false;
|
||||
|
||||
const auto& regs{maxwell3d.regs};
|
||||
const auto gpu_addr{regs.zeta.Address()};
|
||||
@@ -195,13 +193,12 @@ public:
|
||||
SetEmptyDepthBuffer();
|
||||
return {};
|
||||
}
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
SetEmptyDepthBuffer();
|
||||
return {};
|
||||
}
|
||||
const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)};
|
||||
const auto depth_params{SurfaceParams::CreateForDepthBuffer(maxwell3d)};
|
||||
auto surface_view = GetSurface(gpu_addr, *cpu_addr, depth_params, preserve_contents, true);
|
||||
if (depth_buffer.target)
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
@@ -215,7 +212,6 @@ public:
|
||||
TView GetColorBufferSurface(std::size_t index, bool preserve_contents) {
|
||||
std::lock_guard lock{mutex};
|
||||
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
|
||||
auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
if (!maxwell3d.dirty.flags[VideoCommon::Dirty::ColorBuffer0 + index]) {
|
||||
return render_targets[index].view;
|
||||
}
|
||||
@@ -235,15 +231,14 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
SetEmptyColorBuffer(index);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto surface_view =
|
||||
GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
GetSurface(gpu_addr, *cpu_addr, SurfaceParams::CreateForFramebuffer(maxwell3d, index),
|
||||
preserve_contents, true);
|
||||
if (render_targets[index].target) {
|
||||
auto& surface = render_targets[index].target;
|
||||
@@ -300,9 +295,8 @@ public:
|
||||
const GPUVAddr dst_gpu_addr = dst_config.Address();
|
||||
DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
|
||||
|
||||
const auto& memory_manager = system.GPU().MemoryManager();
|
||||
const std::optional<VAddr> dst_cpu_addr = memory_manager.GpuToCpuAddress(dst_gpu_addr);
|
||||
const std::optional<VAddr> src_cpu_addr = memory_manager.GpuToCpuAddress(src_gpu_addr);
|
||||
const std::optional<VAddr> dst_cpu_addr = gpu_memory.GpuToCpuAddress(dst_gpu_addr);
|
||||
const std::optional<VAddr> src_cpu_addr = gpu_memory.GpuToCpuAddress(src_gpu_addr);
|
||||
std::pair dst_surface = GetSurface(dst_gpu_addr, *dst_cpu_addr, dst_params, true, false);
|
||||
TView src_surface = GetSurface(src_gpu_addr, *src_cpu_addr, src_params, true, false).second;
|
||||
ImageBlit(src_surface, dst_surface.second, copy_config);
|
||||
@@ -358,9 +352,11 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer,
|
||||
bool is_astc_supported)
|
||||
: system{system}, is_astc_supported{is_astc_supported}, rasterizer{rasterizer} {
|
||||
explicit TextureCache(VideoCore::RasterizerInterface& rasterizer_,
|
||||
Tegra::Engines::Maxwell3D& maxwell3d_, Tegra::MemoryManager& gpu_memory_,
|
||||
bool is_astc_supported_)
|
||||
: is_astc_supported{is_astc_supported_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
|
||||
gpu_memory{gpu_memory_} {
|
||||
for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) {
|
||||
SetEmptyColorBuffer(i);
|
||||
}
|
||||
@@ -395,7 +391,7 @@ protected:
|
||||
virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0;
|
||||
|
||||
void ManageRenderTargetUnregister(TSurface& surface) {
|
||||
auto& dirty = system.GPU().Maxwell3D().dirty;
|
||||
auto& dirty = maxwell3d.dirty;
|
||||
const u32 index = surface->GetRenderTarget();
|
||||
if (index == DEPTH_RT) {
|
||||
dirty.flags[VideoCommon::Dirty::ZetaBuffer] = true;
|
||||
@@ -408,8 +404,7 @@ protected:
|
||||
void Register(TSurface surface) {
|
||||
const GPUVAddr gpu_addr = surface->GetGpuAddr();
|
||||
const std::size_t size = surface->GetSizeInBytes();
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
if (!cpu_addr) {
|
||||
LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}",
|
||||
gpu_addr);
|
||||
@@ -459,7 +454,6 @@ protected:
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
const bool is_astc_supported;
|
||||
|
||||
private:
|
||||
@@ -954,8 +948,7 @@ private:
|
||||
* @param params The parameters on the candidate surface.
|
||||
**/
|
||||
Deduction DeduceSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) {
|
||||
const std::optional<VAddr> cpu_addr =
|
||||
system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr);
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
||||
|
||||
if (!cpu_addr) {
|
||||
Deduction result{};
|
||||
@@ -1112,7 +1105,7 @@ private:
|
||||
|
||||
void LoadSurface(const TSurface& surface) {
|
||||
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
|
||||
surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache);
|
||||
surface->LoadBuffer(gpu_memory, staging_cache);
|
||||
surface->UploadTexture(staging_cache.GetBuffer(0));
|
||||
surface->MarkAsModified(false, Tick());
|
||||
}
|
||||
@@ -1123,7 +1116,7 @@ private:
|
||||
}
|
||||
staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes());
|
||||
surface->DownloadTexture(staging_cache.GetBuffer(0));
|
||||
surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache);
|
||||
surface->FlushBuffer(gpu_memory, staging_cache);
|
||||
surface->MarkAsModified(false, Tick());
|
||||
}
|
||||
|
||||
@@ -1253,6 +1246,8 @@ private:
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||
Tegra::MemoryManager& gpu_memory;
|
||||
|
||||
FormatLookupTable format_lookup_table;
|
||||
FormatCompatibility format_compatibility;
|
||||
|
||||
@@ -21,14 +21,17 @@ namespace {
|
||||
std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
|
||||
Core::System& system, Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> context) {
|
||||
auto& telemetry_session = system.TelemetrySession();
|
||||
auto& cpu_memory = system.Memory();
|
||||
|
||||
switch (Settings::values.renderer_backend.GetValue()) {
|
||||
case Settings::RendererBackend::OpenGL:
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(system, emu_window, gpu,
|
||||
std::move(context));
|
||||
return std::make_unique<OpenGL::RendererOpenGL>(telemetry_session, emu_window, cpu_memory,
|
||||
gpu, std::move(context));
|
||||
#ifdef HAS_VULKAN
|
||||
case Settings::RendererBackend::Vulkan:
|
||||
return std::make_unique<Vulkan::RendererVulkan>(system, emu_window, gpu,
|
||||
std::move(context));
|
||||
return std::make_unique<Vulkan::RendererVulkan>(telemetry_session, emu_window, cpu_memory,
|
||||
gpu, std::move(context));
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
@@ -63,7 +64,8 @@ void EmuThread::run() {
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
system.Renderer().Rasterizer().LoadDiskResources(
|
||||
stop_run, [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||
system.CurrentProcess()->GetTitleID(), stop_run,
|
||||
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
|
||||
emit LoadProgress(stage, value, total);
|
||||
});
|
||||
|
||||
|
||||
@@ -646,10 +646,10 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
|
||||
const auto& device = input_devices[ui->comboDevices->currentIndex()];
|
||||
auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
|
||||
auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
|
||||
for (int i = 0; i < buttons_param.size(); ++i) {
|
||||
for (std::size_t i = 0; i < buttons_param.size(); ++i) {
|
||||
buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
|
||||
}
|
||||
for (int i = 0; i < analogs_param.size(); ++i) {
|
||||
for (std::size_t i = 0; i < analogs_param.size(); ++i) {
|
||||
analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
|
||||
}
|
||||
|
||||
|
||||
+18
-14
@@ -49,10 +49,10 @@ class GameListItem : public QStandardItem {
|
||||
|
||||
public:
|
||||
// used to access type from item index
|
||||
static const int TypeRole = Qt::UserRole + 1;
|
||||
static const int SortRole = Qt::UserRole + 2;
|
||||
static constexpr int TypeRole = Qt::UserRole + 1;
|
||||
static constexpr int SortRole = Qt::UserRole + 2;
|
||||
GameListItem() = default;
|
||||
GameListItem(const QString& string) : QStandardItem(string) {
|
||||
explicit GameListItem(const QString& string) : QStandardItem(string) {
|
||||
setData(string, SortRole);
|
||||
}
|
||||
};
|
||||
@@ -65,10 +65,10 @@ public:
|
||||
*/
|
||||
class GameListItemPath : public GameListItem {
|
||||
public:
|
||||
static const int TitleRole = SortRole + 1;
|
||||
static const int FullPathRole = SortRole + 2;
|
||||
static const int ProgramIdRole = SortRole + 3;
|
||||
static const int FileTypeRole = SortRole + 4;
|
||||
static constexpr int TitleRole = SortRole + 1;
|
||||
static constexpr int FullPathRole = SortRole + 2;
|
||||
static constexpr int ProgramIdRole = SortRole + 3;
|
||||
static constexpr int FileTypeRole = SortRole + 4;
|
||||
|
||||
GameListItemPath() = default;
|
||||
GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
|
||||
@@ -110,18 +110,22 @@ public:
|
||||
const auto& row1 = row_data.at(UISettings::values.row_1_text_id);
|
||||
const int row2_id = UISettings::values.row_2_text_id;
|
||||
|
||||
if (role == SortRole)
|
||||
if (role == SortRole) {
|
||||
return row1.toLower();
|
||||
}
|
||||
|
||||
if (row2_id == 4) // None
|
||||
// None
|
||||
if (row2_id == 4) {
|
||||
return row1;
|
||||
}
|
||||
|
||||
const auto& row2 = row_data.at(row2_id);
|
||||
|
||||
if (row1 == row2)
|
||||
if (row1 == row2) {
|
||||
return row1;
|
||||
}
|
||||
|
||||
return QString(row1 + QStringLiteral("\n ") + row2);
|
||||
return QStringLiteral("%1\n %2").arg(row1, row2);
|
||||
}
|
||||
|
||||
return GameListItem::data(role);
|
||||
@@ -131,7 +135,7 @@ public:
|
||||
class GameListItemCompat : public GameListItem {
|
||||
Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
|
||||
public:
|
||||
static const int CompatNumberRole = SortRole;
|
||||
static constexpr int CompatNumberRole = SortRole;
|
||||
GameListItemCompat() = default;
|
||||
explicit GameListItemCompat(const QString& compatibility) {
|
||||
setData(type(), TypeRole);
|
||||
@@ -181,7 +185,7 @@ public:
|
||||
*/
|
||||
class GameListItemSize : public GameListItem {
|
||||
public:
|
||||
static const int SizeRole = SortRole;
|
||||
static constexpr int SizeRole = SortRole;
|
||||
|
||||
GameListItemSize() = default;
|
||||
explicit GameListItemSize(const qulonglong size_bytes) {
|
||||
@@ -217,7 +221,7 @@ public:
|
||||
|
||||
class GameListDir : public GameListItem {
|
||||
public:
|
||||
static const int GameDirRole = Qt::UserRole + 2;
|
||||
static constexpr int GameDirRole = Qt::UserRole + 2;
|
||||
|
||||
explicit GameListDir(UISettings::GameDir& directory,
|
||||
GameListItemType dir_type = GameListItemType::CustomDir)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
@@ -235,7 +236,9 @@ int main(int argc, char** argv) {
|
||||
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
|
||||
system.GPU().Start();
|
||||
|
||||
system.Renderer().Rasterizer().LoadDiskResources();
|
||||
system.Renderer().Rasterizer().LoadDiskResources(
|
||||
system.CurrentProcess()->GetTitleID(), false,
|
||||
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
||||
|
||||
std::thread render_thread([&emu_window] { emu_window->Present(); });
|
||||
system.Run();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user