Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f39650a98 | ||
|
|
c554b388e3 | ||
|
|
ce6a034b1c | ||
|
|
68b5902994 | ||
|
|
8573360649 | ||
|
|
f5d28bca9e |
No files matched your search
@@ -85,6 +85,7 @@ if (MSVC)
|
||||
/wd4100 # 'identifier': unreferenced formal parameter
|
||||
/wd4324 # 'struct_name': structure was padded due to __declspec(align())
|
||||
/wd4201 # nonstandard extension used : nameless struct/union
|
||||
/wd4702 # unreachable code (when used with LTO)
|
||||
)
|
||||
|
||||
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
|
||||
|
||||
@@ -88,8 +88,13 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
|
||||
return mailbox.Receive(dir, block);
|
||||
}
|
||||
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept {
|
||||
command_buffers[session_id] = buffer;
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept {
|
||||
command_buffers[session_id].buffer = buffer;
|
||||
command_buffers[session_id].size = size;
|
||||
command_buffers[session_id].time_limit = time_limit;
|
||||
command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
|
||||
command_buffers[session_id].reset_buffer = reset;
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
||||
|
||||
@@ -75,7 +75,8 @@ public:
|
||||
void Send(Direction dir, MailboxMessage message);
|
||||
MailboxMessage Receive(Direction dir, bool block = true);
|
||||
|
||||
void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept;
|
||||
void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept;
|
||||
u32 GetRemainCommandCount(s32 session_id) const noexcept;
|
||||
void ClearRemainCommandCount(s32 session_id) noexcept;
|
||||
u64 GetRenderingStartTick(s32 session_id) const noexcept;
|
||||
|
||||
@@ -37,11 +37,6 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
|
||||
return command_count - processed_command_count;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
}
|
||||
|
||||
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
|
||||
return stream;
|
||||
}
|
||||
|
||||
@@ -56,14 +56,6 @@ public:
|
||||
*/
|
||||
u32 GetRemainingCommandCount() const;
|
||||
|
||||
/**
|
||||
* Set the command buffer.
|
||||
*
|
||||
* @param buffer - The buffer to use.
|
||||
* @param size - The size of the buffer.
|
||||
*/
|
||||
void SetBuffer(CpuAddr buffer, u64 size);
|
||||
|
||||
/**
|
||||
* Get the stream for this command list.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,12 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
|
||||
@@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_process{
|
||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
|
||||
if (samples_to_process == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
@@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
auto& voice_state{*args.voice_state};
|
||||
auto remaining_sample_count{args.sample_count};
|
||||
auto fraction{voice_state.fraction};
|
||||
|
||||
const auto sample_rate_ratio{
|
||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
||||
args.pitch};
|
||||
const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
|
||||
(f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
|
||||
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
|
||||
|
||||
if (size_required < 0) {
|
||||
@@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
auto end_offset{wavebuffer.end_offset};
|
||||
|
||||
if (wavebuffer.loop && voice_state.loop_count > 0 &&
|
||||
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
|
||||
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
|
||||
start_offset = wavebuffer.loop_start_offset;
|
||||
end_offset = wavebuffer.loop_end_offset;
|
||||
}
|
||||
|
||||
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read}};
|
||||
DecodeArg decode_arg{
|
||||
.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read},
|
||||
};
|
||||
|
||||
s32 samples_decoded{0};
|
||||
|
||||
@@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
temp_buffer_pos += samples_decoded;
|
||||
offset += samples_decoded;
|
||||
|
||||
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
|
||||
offset = 0;
|
||||
if (!wavebuffer.loop) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
if (samples_decoded && offset < end_offset - start_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
} else {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
offset = 0;
|
||||
if (wavebuffer.loop) {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
} else {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,12 @@ void PcmFloatDataSourceVersion1Command::Process(
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
|
||||
@@ -23,6 +23,12 @@ void PcmInt16DataSourceVersion1Command::Process(
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
|
||||
@@ -609,17 +609,11 @@ void System::SendCommandToDsp() {
|
||||
time_limit_percent = 70.0f;
|
||||
}
|
||||
|
||||
AudioRenderer::CommandBuffer command_buffer{
|
||||
.buffer{translated_addr},
|
||||
.size{command_size},
|
||||
.time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))},
|
||||
.applet_resource_user_id{applet_resource_user_id},
|
||||
.reset_buffer{reset_command_buffers},
|
||||
};
|
||||
|
||||
audio_renderer.SetCommandBuffer(session_id, command_buffer);
|
||||
auto time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
||||
applet_resource_user_id, reset_command_buffers);
|
||||
reset_command_buffers = false;
|
||||
command_buffer_size = command_size;
|
||||
if (remaining_command_count == 0) {
|
||||
|
||||
@@ -406,6 +406,7 @@ struct System::Impl {
|
||||
gpu_core->NotifyShutdown();
|
||||
}
|
||||
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendApplication(true);
|
||||
if (services) {
|
||||
services->KillNVNFlinger();
|
||||
@@ -427,6 +428,7 @@ struct System::Impl {
|
||||
debugger.reset();
|
||||
kernel.Shutdown();
|
||||
memory.Reset();
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
|
||||
@@ -48,15 +48,32 @@ enum class CallType {
|
||||
|
||||
using socklen_t = int;
|
||||
|
||||
SOCKET interrupt_socket = static_cast<SOCKET>(-1);
|
||||
|
||||
void InterruptSocketOperations() {
|
||||
closesocket(interrupt_socket);
|
||||
}
|
||||
|
||||
void AcknowledgeInterrupt() {
|
||||
interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
WSADATA wsa_data;
|
||||
(void)WSAStartup(MAKEWORD(2, 2), &wsa_data);
|
||||
|
||||
AcknowledgeInterrupt();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
InterruptSocketOperations();
|
||||
WSACleanup();
|
||||
}
|
||||
|
||||
SOCKET GetInterruptSocket() {
|
||||
return interrupt_socket;
|
||||
}
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -157,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD;
|
||||
constexpr int SD_SEND = SHUT_WR;
|
||||
constexpr int SD_BOTH = SHUT_RDWR;
|
||||
|
||||
void Initialize() {}
|
||||
int interrupt_pipe_fd[2] = {-1, -1};
|
||||
|
||||
void Finalize() {}
|
||||
void Initialize() {
|
||||
if (pipe(interrupt_pipe_fd) != 0) {
|
||||
LOG_ERROR(Network, "Failed to create interrupt pipe!");
|
||||
}
|
||||
int flags = fcntl(interrupt_pipe_fd[0], F_GETFL);
|
||||
ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0,
|
||||
"Failed to set nonblocking state for interrupt pipe");
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
if (interrupt_pipe_fd[0] >= 0) {
|
||||
close(interrupt_pipe_fd[0]);
|
||||
}
|
||||
if (interrupt_pipe_fd[1] >= 0) {
|
||||
close(interrupt_pipe_fd[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void InterruptSocketOperations() {
|
||||
u8 value = 0;
|
||||
ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1);
|
||||
}
|
||||
|
||||
void AcknowledgeInterrupt() {
|
||||
u8 value = 0;
|
||||
ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value));
|
||||
if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
SOCKET GetInterruptSocket() {
|
||||
return interrupt_pipe_fd[0];
|
||||
}
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
@@ -490,6 +540,14 @@ NetworkInstance::~NetworkInstance() {
|
||||
Finalize();
|
||||
}
|
||||
|
||||
void CancelPendingSocketOperations() {
|
||||
InterruptSocketOperations();
|
||||
}
|
||||
|
||||
void RestartSocketOperations() {
|
||||
AcknowledgeInterrupt();
|
||||
}
|
||||
|
||||
std::optional<IPv4Address> GetHostIPv4Address() {
|
||||
const auto network_interface = Network::GetSelectedNetworkInterface();
|
||||
if (!network_interface.has_value()) {
|
||||
@@ -560,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
return result;
|
||||
});
|
||||
|
||||
const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout);
|
||||
host_pollfds.push_back(WSAPOLLFD{
|
||||
.fd = GetInterruptSocket(),
|
||||
.events = POLLIN,
|
||||
.revents = 0,
|
||||
});
|
||||
|
||||
const int result =
|
||||
WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout);
|
||||
if (result == 0) {
|
||||
ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(),
|
||||
[](WSAPOLLFD fd) { return fd.revents == 0; }));
|
||||
@@ -627,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
|
||||
std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
|
||||
sockaddr_in addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
|
||||
std::vector<WSAPOLLFD> host_pollfds{
|
||||
WSAPOLLFD{fd, POLLIN, 0},
|
||||
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const int pollres =
|
||||
WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1);
|
||||
if (host_pollfds[1].revents != 0) {
|
||||
// Interrupt signaled before a client could be accepted, break
|
||||
return {AcceptResult{}, Errno::AGAIN};
|
||||
}
|
||||
if (pollres > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen);
|
||||
|
||||
if (new_socket == INVALID_SOCKET) {
|
||||
|
||||
@@ -96,6 +96,9 @@ public:
|
||||
~NetworkInstance();
|
||||
};
|
||||
|
||||
void CancelPendingSocketOperations();
|
||||
void RestartSocketOperations();
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr IPv4Address TranslateIPv4(in_addr addr) {
|
||||
auto& bytes = addr.S_un.S_un_b;
|
||||
|
||||
@@ -204,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
|
||||
if (def.count > 1) {
|
||||
throw NotImplementedException("Indirect texture sample");
|
||||
}
|
||||
const Id sampler_id{def.id};
|
||||
const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
|
||||
return ctx.OpImage(ctx.image_buffer_type, id);
|
||||
return ctx.OpLoad(ctx.image_buffer_type, def.id);
|
||||
} else {
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
if (def.count > 1) {
|
||||
|
||||
@@ -74,6 +74,11 @@ spv::ImageFormat GetImageFormat(ImageFormat format) {
|
||||
throw InvalidArgument("Invalid image format {}", format);
|
||||
}
|
||||
|
||||
spv::ImageFormat GetImageFormatForBuffer(ImageFormat format) {
|
||||
const auto spv_format = GetImageFormat(format);
|
||||
return spv_format == spv::ImageFormat::Unknown ? spv::ImageFormat::R32ui : spv_format;
|
||||
}
|
||||
|
||||
Id ImageType(EmitContext& ctx, const ImageDescriptor& desc) {
|
||||
const spv::ImageFormat format{GetImageFormat(desc.format)};
|
||||
const Id type{ctx.U32[1]};
|
||||
@@ -1242,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
|
||||
}
|
||||
const spv::ImageFormat format{spv::ImageFormat::Unknown};
|
||||
image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
|
||||
sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
|
||||
|
||||
const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)};
|
||||
const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
|
||||
texture_buffers.reserve(info.texture_buffer_descriptors.size());
|
||||
for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
|
||||
if (desc.count != 1) {
|
||||
@@ -1271,7 +1275,7 @@ void EmitContext::DefineImageBuffers(const Info& info, u32& binding) {
|
||||
if (desc.count != 1) {
|
||||
throw NotImplementedException("Array of image buffers");
|
||||
}
|
||||
const spv::ImageFormat format{GetImageFormat(desc.format)};
|
||||
const spv::ImageFormat format{GetImageFormatForBuffer(desc.format)};
|
||||
const Id image_type{TypeImage(U32[1], spv::Dim::Buffer, false, false, false, 2, format)};
|
||||
const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)};
|
||||
const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)};
|
||||
|
||||
@@ -206,7 +206,6 @@ public:
|
||||
Id output_u32{};
|
||||
|
||||
Id image_buffer_type{};
|
||||
Id sampled_texture_buffer_type{};
|
||||
Id image_u32{};
|
||||
|
||||
std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
|
||||
|
||||
Reference in new issue
Block a user