mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
Remove unused common classes
This commit is contained in:
parent
9058a79184
commit
b9e0c329d0
|
@ -1,6 +1,4 @@
|
||||||
set(SRCS
|
set(SRCS
|
||||||
audio.cpp
|
|
||||||
audio.h
|
|
||||||
audio_stream.cpp
|
audio_stream.cpp
|
||||||
audio_stream.h
|
audio_stream.h
|
||||||
bitfield.h
|
bitfield.h
|
||||||
|
@ -8,31 +6,15 @@ set(SRCS
|
||||||
cd_image.h
|
cd_image.h
|
||||||
cd_xa.cpp
|
cd_xa.cpp
|
||||||
cd_xa.h
|
cd_xa.h
|
||||||
display.cpp
|
|
||||||
display.h
|
|
||||||
display_renderer.cpp
|
|
||||||
display_renderer.h
|
|
||||||
display_timing.cpp
|
|
||||||
display_timing.h
|
|
||||||
fastjmp.h
|
|
||||||
gl_program.cpp
|
gl_program.cpp
|
||||||
gl_program.h
|
gl_program.h
|
||||||
gl_texture.cpp
|
gl_texture.cpp
|
||||||
gl_texture.h
|
gl_texture.h
|
||||||
hdd_image.cpp
|
|
||||||
hdd_image.h
|
|
||||||
jit_code_buffer.cpp
|
jit_code_buffer.cpp
|
||||||
jit_code_buffer.h
|
jit_code_buffer.h
|
||||||
object.cpp
|
|
||||||
object.h
|
|
||||||
object_type_info.cpp
|
|
||||||
object_type_info.h
|
|
||||||
property.cpp
|
|
||||||
property.h
|
|
||||||
state_wrapper.cpp
|
state_wrapper.cpp
|
||||||
state_wrapper.h
|
state_wrapper.h
|
||||||
types.h
|
types.h
|
||||||
type_registry.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(common ${SRCS})
|
add_library(common ${SRCS})
|
||||||
|
@ -40,23 +22,3 @@ add_library(common ${SRCS})
|
||||||
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
target_link_libraries(common YBaseLib glad libcue Threads::Threads)
|
target_link_libraries(common YBaseLib glad libcue Threads::Threads)
|
||||||
|
|
||||||
if(ENABLE_OPENGL)
|
|
||||||
target_sources(common PRIVATE display_renderer_gl.cpp display_renderer_gl.h)
|
|
||||||
target_link_libraries(common glad)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
target_sources(common PRIVATE display_renderer_d3d.cpp display_renderer_d3d.h)
|
|
||||||
target_link_libraries(common d3d11.lib)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC)
|
|
||||||
enable_language(ASM_MASM)
|
|
||||||
if(CMAKE_ASM_MASM_COMPILER_WORKS)
|
|
||||||
target_sources(common PRIVATE fastjmp.asm)
|
|
||||||
else()
|
|
||||||
message(ERROR "MASM assembler does not work")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
|
@ -1,372 +0,0 @@
|
||||||
#include "audio.h"
|
|
||||||
#include "samplerate.h"
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace Audio {
|
|
||||||
|
|
||||||
size_t GetBytesPerSample(SampleFormat format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case SampleFormat::Signed8:
|
|
||||||
case SampleFormat::Unsigned8:
|
|
||||||
return sizeof(u8);
|
|
||||||
|
|
||||||
case SampleFormat::Signed16:
|
|
||||||
case SampleFormat::Unsigned16:
|
|
||||||
return sizeof(u16);
|
|
||||||
|
|
||||||
case SampleFormat::Signed32:
|
|
||||||
return sizeof(s32);
|
|
||||||
|
|
||||||
case SampleFormat::Float32:
|
|
||||||
return sizeof(float);
|
|
||||||
}
|
|
||||||
|
|
||||||
Panic("Unhandled format");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::Mixer(float output_sample_rate) : m_output_sample_rate(output_sample_rate)
|
|
||||||
{
|
|
||||||
// Render/mix buffers are allocated on-demand.
|
|
||||||
m_output_buffer = std::make_unique<CircularBuffer>(size_t(output_sample_rate * OutputBufferLengthInSeconds) *
|
|
||||||
NumOutputChannels * sizeof(OutputFormatType));
|
|
||||||
}
|
|
||||||
|
|
||||||
Mixer::~Mixer() {}
|
|
||||||
|
|
||||||
Channel* Mixer::CreateChannel(const char* name, float sample_rate, SampleFormat format, size_t channels)
|
|
||||||
{
|
|
||||||
Assert(!GetChannelByName(name));
|
|
||||||
|
|
||||||
std::unique_ptr<Channel> channel =
|
|
||||||
std::make_unique<Channel>(name, m_output_sample_rate, sample_rate, format, channels);
|
|
||||||
m_channels.push_back(std::move(channel));
|
|
||||||
return m_channels.back().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::RemoveChannel(Channel* channel)
|
|
||||||
{
|
|
||||||
for (auto iter = m_channels.begin(); iter != m_channels.end(); iter++)
|
|
||||||
{
|
|
||||||
if (iter->get() == channel)
|
|
||||||
{
|
|
||||||
m_channels.erase(iter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Panic("Removing unknown channel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Channel* Mixer::GetChannelByName(const char* name)
|
|
||||||
{
|
|
||||||
for (auto& channel : m_channels)
|
|
||||||
{
|
|
||||||
if (channel->GetName().Compare(name))
|
|
||||||
return channel.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::ClearBuffers()
|
|
||||||
{
|
|
||||||
for (const auto& channel : m_channels)
|
|
||||||
channel->ClearBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::CheckRenderBufferSize(size_t num_samples)
|
|
||||||
{
|
|
||||||
size_t buffer_size = num_samples * NumOutputChannels * sizeof(OutputFormatType);
|
|
||||||
if (m_render_buffer.size() < buffer_size)
|
|
||||||
m_render_buffer.resize(buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioBuffer::AudioBuffer(size_t size) : m_buffer(size) {}
|
|
||||||
|
|
||||||
size_t AudioBuffer::GetBufferUsed() const
|
|
||||||
{
|
|
||||||
return m_used;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t AudioBuffer::GetContiguousBufferSpace() const
|
|
||||||
{
|
|
||||||
return m_buffer.size() - m_used;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioBuffer::Clear()
|
|
||||||
{
|
|
||||||
m_used = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioBuffer::Read(void* dst, size_t len)
|
|
||||||
{
|
|
||||||
if (len > m_used)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::memcpy(dst, m_buffer.data(), len);
|
|
||||||
m_used -= len;
|
|
||||||
if (m_used > 0)
|
|
||||||
std::memmove(m_buffer.data(), m_buffer.data() + len, m_used);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioBuffer::GetWritePointer(void** ptr, size_t* len)
|
|
||||||
{
|
|
||||||
size_t free = GetContiguousBufferSpace();
|
|
||||||
if (*len > free)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*len = free;
|
|
||||||
*ptr = m_buffer.data() + m_used;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioBuffer::MoveWritePointer(size_t len)
|
|
||||||
{
|
|
||||||
DebugAssert(m_used + len <= m_buffer.size());
|
|
||||||
m_used += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AudioBuffer::GetReadPointer(const void** ppReadPointer, size_t* pByteCount) const
|
|
||||||
{
|
|
||||||
if (m_used == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*ppReadPointer = m_buffer.data();
|
|
||||||
*pByteCount = m_used;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioBuffer::MoveReadPointer(size_t byteCount)
|
|
||||||
{
|
|
||||||
DebugAssert(byteCount <= m_used);
|
|
||||||
m_used -= byteCount;
|
|
||||||
if (m_used > 0)
|
|
||||||
std::memmove(m_buffer.data(), m_buffer.data() + byteCount, m_used);
|
|
||||||
}
|
|
||||||
|
|
||||||
Channel::Channel(const char* name, float output_sample_rate, float input_sample_rate, SampleFormat format,
|
|
||||||
size_t channels)
|
|
||||||
: m_name(name), m_input_sample_rate(input_sample_rate), m_output_sample_rate(output_sample_rate), m_format(format),
|
|
||||||
m_channels(channels), m_enabled(true), m_input_sample_size(GetBytesPerSample(format)),
|
|
||||||
m_input_frame_size(GetBytesPerSample(format) * channels), m_output_frame_size(sizeof(float) * channels),
|
|
||||||
m_input_buffer(u32(float(InputBufferLengthInSeconds* input_sample_rate)) * channels * m_input_sample_size),
|
|
||||||
m_output_buffer(u32(float(InputBufferLengthInSeconds* output_sample_rate)) * channels * sizeof(OutputFormatType)),
|
|
||||||
m_resample_buffer(u32(float(InputBufferLengthInSeconds* output_sample_rate)) * channels),
|
|
||||||
m_resample_ratio(double(output_sample_rate) / double(input_sample_rate)),
|
|
||||||
m_resampler_state(src_new(SRC_SINC_FASTEST, int(channels), nullptr))
|
|
||||||
{
|
|
||||||
Assert(m_resampler_state != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Channel::~Channel()
|
|
||||||
{
|
|
||||||
src_delete(reinterpret_cast<SRC_STATE*>(m_resampler_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Channel::GetFreeInputSamples()
|
|
||||||
{
|
|
||||||
MutexLock lock(m_lock);
|
|
||||||
return m_input_buffer.GetContiguousBufferSpace() / m_input_frame_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* Channel::ReserveInputSamples(size_t sample_count)
|
|
||||||
{
|
|
||||||
void* write_ptr;
|
|
||||||
size_t byte_count = sample_count * m_input_frame_size;
|
|
||||||
|
|
||||||
m_lock.Lock();
|
|
||||||
|
|
||||||
// When the speed limiter is off, we can easily exceed the audio buffer length.
|
|
||||||
// In this case, just destroy the oldest samples, wrapping around.
|
|
||||||
while (!m_input_buffer.GetWritePointer(&write_ptr, &byte_count))
|
|
||||||
{
|
|
||||||
size_t bytes_to_remove = byte_count - m_input_buffer.GetContiguousBufferSpace();
|
|
||||||
m_input_buffer.MoveReadPointer(bytes_to_remove);
|
|
||||||
}
|
|
||||||
|
|
||||||
return write_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::CommitInputSamples(size_t sample_count)
|
|
||||||
{
|
|
||||||
size_t byte_count = sample_count * m_input_frame_size;
|
|
||||||
m_input_buffer.MoveWritePointer(byte_count);
|
|
||||||
|
|
||||||
m_lock.Unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::ReadSamples(float* destination, size_t num_samples)
|
|
||||||
{
|
|
||||||
MutexLock lock(m_lock);
|
|
||||||
|
|
||||||
while (num_samples > 0)
|
|
||||||
{
|
|
||||||
// Can we use what we have buffered?
|
|
||||||
size_t currently_buffered = m_output_buffer.GetBufferUsed() / m_output_frame_size;
|
|
||||||
if (currently_buffered > 0)
|
|
||||||
{
|
|
||||||
size_t to_read = std::min(num_samples, currently_buffered);
|
|
||||||
m_output_buffer.Read(destination, to_read * m_output_frame_size);
|
|
||||||
destination += to_read;
|
|
||||||
num_samples -= to_read;
|
|
||||||
if (num_samples == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resample num_samples samples
|
|
||||||
if (m_input_buffer.GetBufferUsed() > 0)
|
|
||||||
{
|
|
||||||
if (ResampleInput(num_samples))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we hit here, it's because we're out of input data.
|
|
||||||
std::memset(destination, 0, num_samples * m_output_frame_size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::ChangeSampleRate(float new_sample_rate)
|
|
||||||
{
|
|
||||||
MutexLock lock(m_lock);
|
|
||||||
InternalClearBuffer();
|
|
||||||
|
|
||||||
// Calculate the new ratio.
|
|
||||||
m_input_sample_rate = new_sample_rate;
|
|
||||||
m_resample_ratio = double(m_output_sample_rate) / double(new_sample_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::ClearBuffer()
|
|
||||||
{
|
|
||||||
MutexLock lock(m_lock);
|
|
||||||
InternalClearBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Channel::InternalClearBuffer()
|
|
||||||
{
|
|
||||||
m_input_buffer.Clear();
|
|
||||||
src_reset(reinterpret_cast<SRC_STATE*>(m_resampler_state));
|
|
||||||
m_output_buffer.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Channel::ResampleInput(size_t num_output_samples)
|
|
||||||
{
|
|
||||||
const void* in_buf;
|
|
||||||
size_t in_bufsize;
|
|
||||||
size_t in_num_frames;
|
|
||||||
size_t in_num_samples;
|
|
||||||
if (!m_input_buffer.GetReadPointer(&in_buf, &in_bufsize))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
in_num_frames = in_bufsize / m_input_frame_size;
|
|
||||||
in_num_samples = in_num_frames * m_channels;
|
|
||||||
if (in_num_frames == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Cap output samples at buffer size.
|
|
||||||
num_output_samples = std::min(num_output_samples, m_output_buffer.GetContiguousBufferSpace() / m_output_frame_size);
|
|
||||||
Assert((num_output_samples * m_channels) < m_resample_buffer.size());
|
|
||||||
|
|
||||||
// Only use as many input samples as needed.
|
|
||||||
void* out_buf;
|
|
||||||
size_t out_bufsize = num_output_samples * m_output_frame_size;
|
|
||||||
if (!m_output_buffer.GetWritePointer(&out_buf, &out_bufsize))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Set up resampling.
|
|
||||||
SRC_DATA resample_data;
|
|
||||||
resample_data.data_out = reinterpret_cast<float*>(out_buf);
|
|
||||||
resample_data.output_frames = static_cast<long>(num_output_samples);
|
|
||||||
resample_data.input_frames_used = 0;
|
|
||||||
resample_data.output_frames_gen = 0;
|
|
||||||
resample_data.end_of_input = 0;
|
|
||||||
resample_data.src_ratio = m_resample_ratio;
|
|
||||||
|
|
||||||
// Convert from whatever format the input is in to float.
|
|
||||||
switch (m_format)
|
|
||||||
{
|
|
||||||
case SampleFormat::Signed8:
|
|
||||||
{
|
|
||||||
const s8* in_samples_typed = reinterpret_cast<const s8*>(in_buf);
|
|
||||||
for (size_t i = 0; i < in_num_samples; i++)
|
|
||||||
m_resample_buffer[i] = float(in_samples_typed[i]) / float(0x80);
|
|
||||||
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = m_resample_buffer.data();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SampleFormat::Unsigned8:
|
|
||||||
{
|
|
||||||
const s8* in_samples_typed = reinterpret_cast<const s8*>(in_buf);
|
|
||||||
for (size_t i = 0; i < in_num_samples; i++)
|
|
||||||
m_resample_buffer[i] = float(int(in_samples_typed[i]) - 128) / float(0x80);
|
|
||||||
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = m_resample_buffer.data();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SampleFormat::Signed16:
|
|
||||||
src_short_to_float_array(reinterpret_cast<const short*>(in_buf), m_resample_buffer.data(), int(in_num_samples));
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = m_resample_buffer.data();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SampleFormat::Unsigned16:
|
|
||||||
{
|
|
||||||
const u16* in_samples_typed = reinterpret_cast<const u16*>(in_buf);
|
|
||||||
for (size_t i = 0; i < in_num_samples; i++)
|
|
||||||
m_resample_buffer[i] = float(int(in_samples_typed[i]) - 32768) / float(0x8000);
|
|
||||||
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = m_resample_buffer.data();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SampleFormat::Signed32:
|
|
||||||
src_int_to_float_array(reinterpret_cast<const int*>(in_buf), m_resample_buffer.data(), int(in_num_samples));
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = m_resample_buffer.data();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SampleFormat::Float32:
|
|
||||||
default:
|
|
||||||
resample_data.input_frames = long(in_num_frames);
|
|
||||||
resample_data.data_in = reinterpret_cast<const float*>(in_buf);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actually perform the resampling.
|
|
||||||
int process_result = src_process(reinterpret_cast<SRC_STATE*>(m_resampler_state), &resample_data);
|
|
||||||
Assert(process_result == 0);
|
|
||||||
|
|
||||||
// Update buffer pointers.
|
|
||||||
m_input_buffer.MoveReadPointer(size_t(resample_data.input_frames_used) * m_input_frame_size);
|
|
||||||
m_output_buffer.MoveWritePointer(size_t(resample_data.output_frames_gen) * m_output_frame_size);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
NullMixer::NullMixer() : Mixer(44100) {}
|
|
||||||
|
|
||||||
NullMixer::~NullMixer() {}
|
|
||||||
|
|
||||||
std::unique_ptr<Mixer> NullMixer::Create()
|
|
||||||
{
|
|
||||||
return std::make_unique<NullMixer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NullMixer::RenderSamples(size_t output_samples)
|
|
||||||
{
|
|
||||||
CheckRenderBufferSize(output_samples);
|
|
||||||
|
|
||||||
// Consume everything from the input buffers.
|
|
||||||
for (auto& channel : m_channels)
|
|
||||||
channel->ReadSamples(m_render_buffer.data(), output_samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Audio
|
|
|
@ -1,184 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "YBaseLib/CircularBuffer.h"
|
|
||||||
#include "YBaseLib/Mutex.h"
|
|
||||||
#include "YBaseLib/MutexLock.h"
|
|
||||||
#include "YBaseLib/String.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
namespace Audio {
|
|
||||||
|
|
||||||
class Channel;
|
|
||||||
class Mixer;
|
|
||||||
|
|
||||||
enum class SampleFormat
|
|
||||||
{
|
|
||||||
Signed8,
|
|
||||||
Unsigned8,
|
|
||||||
Signed16,
|
|
||||||
Unsigned16,
|
|
||||||
Signed32,
|
|
||||||
Float32
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constants for the maximums we support.
|
|
||||||
constexpr size_t NumOutputChannels = 2;
|
|
||||||
|
|
||||||
// We buffer one second of data either way.
|
|
||||||
constexpr float InputBufferLengthInSeconds = 1.0f;
|
|
||||||
constexpr float OutputBufferLengthInSeconds = 1.0f;
|
|
||||||
|
|
||||||
// Audio render frequency. We render the elapsed simulated time worth of audio at this interval.
|
|
||||||
// Currently it is every 50ms, or 20hz. For audio channels, it's recommended to render at twice this
|
|
||||||
// frequency in order to ensure that there is always data ready. This could also be mitigated by buffering.
|
|
||||||
constexpr u32 MixFrequency = 20;
|
|
||||||
constexpr SimulationTime MixInterval = SimulationTime(1000000000) / SimulationTime(MixFrequency);
|
|
||||||
|
|
||||||
// We buffer 10ms of input before rendering any samples, that way we don't get buffer underruns.
|
|
||||||
constexpr SimulationTime ChannelDelayTimeInSimTime = SimulationTime(10000000);
|
|
||||||
|
|
||||||
// Output format type.
|
|
||||||
constexpr SampleFormat OutputFormat = SampleFormat::Float32;
|
|
||||||
using OutputFormatType = float;
|
|
||||||
|
|
||||||
// Get the number of bytes for each element of a sample format.
|
|
||||||
size_t GetBytesPerSample(SampleFormat format);
|
|
||||||
|
|
||||||
// Base audio class, handles mixing/resampling
|
|
||||||
class Mixer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Mixer(float output_sample_rate);
|
|
||||||
virtual ~Mixer();
|
|
||||||
|
|
||||||
// Disable all outputs.
|
|
||||||
// This prevents any samples being written to the device, but still consumes samples.
|
|
||||||
bool IsMuted() const { return m_muted; }
|
|
||||||
void SetMuted(bool muted) { m_muted = muted; }
|
|
||||||
|
|
||||||
// Adds a channel to the audio mixer.
|
|
||||||
// This pointer is owned by the audio class.
|
|
||||||
Channel* CreateChannel(const char* name, float sample_rate, SampleFormat format, size_t channels);
|
|
||||||
|
|
||||||
// Drops a channel from the audio mixer.
|
|
||||||
void RemoveChannel(Channel* channel);
|
|
||||||
|
|
||||||
// Looks up channel by name. Shouldn't really be needed.
|
|
||||||
Channel* GetChannelByName(const char* name);
|
|
||||||
|
|
||||||
// Clears all buffers. Use when changing speed limiter state, or loading state.
|
|
||||||
void ClearBuffers();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void CheckRenderBufferSize(size_t num_samples);
|
|
||||||
|
|
||||||
float m_output_sample_rate;
|
|
||||||
float m_output_sample_carry = 0.0f;
|
|
||||||
bool m_muted = false;
|
|
||||||
|
|
||||||
// Input channels.
|
|
||||||
std::vector<std::unique_ptr<Channel>> m_channels;
|
|
||||||
|
|
||||||
// Output buffer.
|
|
||||||
std::vector<OutputFormatType> m_render_buffer;
|
|
||||||
std::unique_ptr<CircularBuffer> m_output_buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AudioBuffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudioBuffer(size_t size);
|
|
||||||
|
|
||||||
size_t GetBufferUsed() const;
|
|
||||||
size_t GetContiguousBufferSpace() const;
|
|
||||||
|
|
||||||
void Clear();
|
|
||||||
|
|
||||||
bool Read(void* dst, size_t len);
|
|
||||||
|
|
||||||
bool GetWritePointer(void** ptr, size_t* len);
|
|
||||||
|
|
||||||
void MoveWritePointer(size_t len);
|
|
||||||
|
|
||||||
bool GetReadPointer(const void** ppReadPointer, size_t* pByteCount) const;
|
|
||||||
|
|
||||||
void MoveReadPointer(size_t byteCount);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<byte> m_buffer;
|
|
||||||
size_t m_used = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A channel, or source of audio for the mixer.
|
|
||||||
class Channel
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Channel(const char* name, float output_sample_rate, float input_sample_rate, SampleFormat format, size_t channels);
|
|
||||||
~Channel();
|
|
||||||
|
|
||||||
const String& GetName() const { return m_name; }
|
|
||||||
float GetSampleRate() const { return m_input_sample_rate; }
|
|
||||||
SampleFormat GetFormat() const { return m_format; }
|
|
||||||
size_t GetChannels() const { return m_channels; }
|
|
||||||
|
|
||||||
// When the channel is disabled, adding samples will have no effect, and it won't affect the output.
|
|
||||||
bool IsEnabled() const { return m_enabled; }
|
|
||||||
void SetEnabled(bool enabled) { m_enabled = enabled; }
|
|
||||||
|
|
||||||
// This sample_count is the number of samples per channel, so two-channel will be half of the total values.
|
|
||||||
size_t GetFreeInputSamples();
|
|
||||||
void* ReserveInputSamples(size_t sample_count);
|
|
||||||
void CommitInputSamples(size_t sample_count);
|
|
||||||
|
|
||||||
// Resamples at most num_output_samples, the actual number can be lower if there isn't enough input data.
|
|
||||||
bool ResampleInput(size_t num_output_samples);
|
|
||||||
|
|
||||||
// Render n output samples. If not enough input data is in the buffer, set to zero.
|
|
||||||
void ReadSamples(float* destination, size_t num_samples);
|
|
||||||
|
|
||||||
// Changes the frequency of the input data. Flushes the resample buffer.
|
|
||||||
void ChangeSampleRate(float new_sample_rate);
|
|
||||||
|
|
||||||
// Clears the buffer. Use when loading state or changing speed limiter.
|
|
||||||
void ClearBuffer();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void InternalClearBuffer();
|
|
||||||
|
|
||||||
String m_name;
|
|
||||||
float m_input_sample_rate;
|
|
||||||
float m_output_sample_rate;
|
|
||||||
SampleFormat m_format;
|
|
||||||
size_t m_channels;
|
|
||||||
bool m_enabled;
|
|
||||||
|
|
||||||
Mutex m_lock;
|
|
||||||
// std::unique_ptr<CircularBuffer> m_input_buffer;
|
|
||||||
size_t m_input_sample_size;
|
|
||||||
size_t m_input_frame_size;
|
|
||||||
size_t m_output_frame_size;
|
|
||||||
AudioBuffer m_input_buffer;
|
|
||||||
AudioBuffer m_output_buffer;
|
|
||||||
std::vector<float> m_resample_buffer;
|
|
||||||
double m_resample_ratio;
|
|
||||||
void* m_resampler_state;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Null audio sink/mixer
|
|
||||||
class NullMixer : public Mixer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NullMixer();
|
|
||||||
virtual ~NullMixer();
|
|
||||||
|
|
||||||
static std::unique_ptr<Mixer> Create();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void RenderSamples(size_t output_samples);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Audio
|
|
|
@ -35,56 +35,31 @@
|
||||||
</ProjectConfiguration>
|
</ProjectConfiguration>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="audio.h" />
|
|
||||||
<ClInclude Include="audio_stream.h" />
|
<ClInclude Include="audio_stream.h" />
|
||||||
<ClInclude Include="bitfield.h" />
|
<ClInclude Include="bitfield.h" />
|
||||||
<ClInclude Include="cd_image.h" />
|
<ClInclude Include="cd_image.h" />
|
||||||
<ClInclude Include="display.h" />
|
|
||||||
<ClInclude Include="display_renderer_d3d.h" />
|
|
||||||
<ClInclude Include="display_renderer.h" />
|
|
||||||
<ClInclude Include="display_renderer_gl.h" />
|
|
||||||
<ClInclude Include="display_timing.h" />
|
|
||||||
<ClInclude Include="fastjmp.h" />
|
|
||||||
<ClInclude Include="fifo_queue.h" />
|
<ClInclude Include="fifo_queue.h" />
|
||||||
<ClInclude Include="gl_program.h" />
|
<ClInclude Include="gl_program.h" />
|
||||||
<ClInclude Include="gl_texture.h" />
|
<ClInclude Include="gl_texture.h" />
|
||||||
<ClInclude Include="hdd_image.h" />
|
|
||||||
<ClInclude Include="jit_code_buffer.h" />
|
<ClInclude Include="jit_code_buffer.h" />
|
||||||
<ClInclude Include="object.h" />
|
|
||||||
<ClInclude Include="object_type_info.h" />
|
|
||||||
<ClInclude Include="property.h" />
|
|
||||||
<ClInclude Include="state_wrapper.h" />
|
<ClInclude Include="state_wrapper.h" />
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
<ClInclude Include="type_registry.h" />
|
|
||||||
<ClInclude Include="cd_xa.h" />
|
<ClInclude Include="cd_xa.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="audio.cpp" />
|
|
||||||
<ClCompile Include="audio_stream.cpp" />
|
<ClCompile Include="audio_stream.cpp" />
|
||||||
<ClCompile Include="cd_image.cpp" />
|
<ClCompile Include="cd_image.cpp" />
|
||||||
<ClCompile Include="cd_image_bin.cpp" />
|
<ClCompile Include="cd_image_bin.cpp" />
|
||||||
<ClCompile Include="cd_image_cuesheet.cpp" />
|
<ClCompile Include="cd_image_cuesheet.cpp" />
|
||||||
<ClCompile Include="display.cpp" />
|
|
||||||
<ClCompile Include="display_renderer_d3d.cpp" />
|
|
||||||
<ClCompile Include="display_renderer.cpp" />
|
|
||||||
<ClCompile Include="display_renderer_gl.cpp" />
|
|
||||||
<ClCompile Include="display_timing.cpp" />
|
|
||||||
<ClCompile Include="gl_program.cpp" />
|
<ClCompile Include="gl_program.cpp" />
|
||||||
<ClCompile Include="gl_texture.cpp" />
|
<ClCompile Include="gl_texture.cpp" />
|
||||||
<ClCompile Include="hdd_image.cpp" />
|
|
||||||
<ClCompile Include="jit_code_buffer.cpp" />
|
<ClCompile Include="jit_code_buffer.cpp" />
|
||||||
<ClCompile Include="object.cpp" />
|
|
||||||
<ClCompile Include="object_type_info.cpp" />
|
|
||||||
<ClCompile Include="property.cpp" />
|
|
||||||
<ClCompile Include="state_wrapper.cpp" />
|
<ClCompile Include="state_wrapper.cpp" />
|
||||||
<ClCompile Include="cd_xa.cpp" />
|
<ClCompile Include="cd_xa.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<MASM Include="fastjmp.asm" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\dep\glad\glad.vcxproj">
|
<ProjectReference Include="..\..\dep\glad\glad.vcxproj">
|
||||||
<Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
|
<Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
|
||||||
|
|
|
@ -3,18 +3,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="bitfield.h" />
|
<ClInclude Include="bitfield.h" />
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
<ClInclude Include="fastjmp.h" />
|
|
||||||
<ClInclude Include="hdd_image.h" />
|
|
||||||
<ClInclude Include="audio.h" />
|
|
||||||
<ClInclude Include="object.h" />
|
|
||||||
<ClInclude Include="object_type_info.h" />
|
|
||||||
<ClInclude Include="property.h" />
|
|
||||||
<ClInclude Include="type_registry.h" />
|
|
||||||
<ClInclude Include="display.h" />
|
|
||||||
<ClInclude Include="display_renderer_d3d.h" />
|
|
||||||
<ClInclude Include="display_renderer.h" />
|
|
||||||
<ClInclude Include="display_renderer_gl.h" />
|
|
||||||
<ClInclude Include="display_timing.h" />
|
|
||||||
<ClInclude Include="jit_code_buffer.h" />
|
<ClInclude Include="jit_code_buffer.h" />
|
||||||
<ClInclude Include="state_wrapper.h" />
|
<ClInclude Include="state_wrapper.h" />
|
||||||
<ClInclude Include="gl_program.h" />
|
<ClInclude Include="gl_program.h" />
|
||||||
|
@ -25,16 +13,6 @@
|
||||||
<ClInclude Include="cd_xa.h" />
|
<ClInclude Include="cd_xa.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="hdd_image.cpp" />
|
|
||||||
<ClCompile Include="audio.cpp" />
|
|
||||||
<ClCompile Include="object_type_info.cpp" />
|
|
||||||
<ClCompile Include="property.cpp" />
|
|
||||||
<ClCompile Include="object.cpp" />
|
|
||||||
<ClCompile Include="display.cpp" />
|
|
||||||
<ClCompile Include="display_renderer_d3d.cpp" />
|
|
||||||
<ClCompile Include="display_renderer.cpp" />
|
|
||||||
<ClCompile Include="display_renderer_gl.cpp" />
|
|
||||||
<ClCompile Include="display_timing.cpp" />
|
|
||||||
<ClCompile Include="jit_code_buffer.cpp" />
|
<ClCompile Include="jit_code_buffer.cpp" />
|
||||||
<ClCompile Include="state_wrapper.cpp" />
|
<ClCompile Include="state_wrapper.cpp" />
|
||||||
<ClCompile Include="gl_program.cpp" />
|
<ClCompile Include="gl_program.cpp" />
|
||||||
|
@ -48,7 +26,4 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<MASM Include="fastjmp.asm" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
|
@ -1,529 +0,0 @@
|
||||||
#include "display.h"
|
|
||||||
#include "YBaseLib/Assert.h"
|
|
||||||
#include "YBaseLib/Math.h"
|
|
||||||
#include "display_renderer.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
Display::Display(DisplayRenderer* manager, const String& name, Type type, u8 priority)
|
|
||||||
: m_renderer(manager), m_name(name), m_type(type), m_priority(priority)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Display::~Display()
|
|
||||||
{
|
|
||||||
m_renderer->RemoveDisplay(this);
|
|
||||||
DestroyFramebuffer(&m_front_buffer);
|
|
||||||
DestroyFramebuffer(&m_back_buffers[0]);
|
|
||||||
DestroyFramebuffer(&m_back_buffers[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SetEnable(bool enabled)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_buffer_lock);
|
|
||||||
if (m_enabled == enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_enabled = enabled;
|
|
||||||
if (enabled)
|
|
||||||
m_renderer->DisplayEnabled(this);
|
|
||||||
else
|
|
||||||
m_renderer->DisplayDisabled(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SetActive(bool active)
|
|
||||||
{
|
|
||||||
m_active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SetDisplayAspectRatio(u32 numerator, u32 denominator)
|
|
||||||
{
|
|
||||||
if (m_display_aspect_numerator == numerator && m_display_aspect_denominator == denominator)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_display_aspect_numerator = numerator;
|
|
||||||
m_display_aspect_denominator = denominator;
|
|
||||||
ResizeDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::ResizeDisplay(u32 width /*= 0*/, u32 height /*= 0*/)
|
|
||||||
{
|
|
||||||
// If width/height == 0, use aspect ratio to calculate size
|
|
||||||
if (width == 0 && height == 0)
|
|
||||||
{
|
|
||||||
// TODO: Remove floating point math here
|
|
||||||
// float pixel_aspect_ratio = static_cast<float>(m_framebuffer_width) / static_cast<float>(m_framebuffer_height);
|
|
||||||
float display_aspect_ratio =
|
|
||||||
static_cast<float>(m_display_aspect_numerator) / static_cast<float>(m_display_aspect_denominator);
|
|
||||||
// float ratio = pixel_aspect_ratio / display_aspect_ratio;
|
|
||||||
m_display_width = std::max(1u, m_framebuffer_width * m_display_scale);
|
|
||||||
m_display_height = std::max(1u, static_cast<u32>(static_cast<float>(m_display_width) / display_aspect_ratio));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DebugAssert(width > 0 && height > 0);
|
|
||||||
m_display_width = width * m_display_scale;
|
|
||||||
m_display_height = height * m_display_scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_renderer->DisplayResized(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::ClearFramebuffer()
|
|
||||||
{
|
|
||||||
if (m_back_buffers[0].width > 0 && m_back_buffers[0].height > 0)
|
|
||||||
std::memset(m_back_buffers[0].data, 0, m_back_buffers[0].stride * m_back_buffers[0].height);
|
|
||||||
|
|
||||||
SwapFramebuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SwapFramebuffer()
|
|
||||||
{
|
|
||||||
// Make it visible to the render thread.
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_buffer_lock);
|
|
||||||
std::swap(m_back_buffers[0].data, m_back_buffers[1].data);
|
|
||||||
std::swap(m_back_buffers[0].palette, m_back_buffers[1].palette);
|
|
||||||
std::swap(m_back_buffers[0].width, m_back_buffers[1].width);
|
|
||||||
std::swap(m_back_buffers[0].height, m_back_buffers[1].height);
|
|
||||||
std::swap(m_back_buffers[0].stride, m_back_buffers[1].stride);
|
|
||||||
std::swap(m_back_buffers[0].format, m_back_buffers[1].format);
|
|
||||||
m_back_buffers[1].dirty = true;
|
|
||||||
m_renderer->DisplayFramebufferSwapped(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure backbuffer is up to date.
|
|
||||||
if (m_back_buffers[0].width != m_framebuffer_width || m_back_buffers[0].height != m_framebuffer_height ||
|
|
||||||
m_back_buffers[0].format != m_framebuffer_format)
|
|
||||||
{
|
|
||||||
AllocateFramebuffer(&m_back_buffers[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddFrameRendered();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Display::UpdateFrontbuffer()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_buffer_lock);
|
|
||||||
if (!m_back_buffers[1].dirty)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
std::swap(m_front_buffer.data, m_back_buffers[1].data);
|
|
||||||
std::swap(m_front_buffer.palette, m_back_buffers[1].palette);
|
|
||||||
std::swap(m_front_buffer.width, m_back_buffers[1].width);
|
|
||||||
std::swap(m_front_buffer.height, m_back_buffers[1].height);
|
|
||||||
std::swap(m_front_buffer.stride, m_back_buffers[1].stride);
|
|
||||||
std::swap(m_front_buffer.format, m_back_buffers[1].format);
|
|
||||||
m_back_buffers[1].dirty = false;
|
|
||||||
m_front_buffer.dirty = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::AllocateFramebuffer(Framebuffer* fbuf)
|
|
||||||
{
|
|
||||||
DestroyFramebuffer(fbuf);
|
|
||||||
|
|
||||||
fbuf->width = m_framebuffer_width;
|
|
||||||
fbuf->height = m_framebuffer_height;
|
|
||||||
fbuf->format = m_framebuffer_format;
|
|
||||||
fbuf->stride = 0;
|
|
||||||
|
|
||||||
if (m_framebuffer_width > 0 && m_framebuffer_height > 0)
|
|
||||||
{
|
|
||||||
switch (m_framebuffer_format)
|
|
||||||
{
|
|
||||||
case FramebufferFormat::RGB8:
|
|
||||||
case FramebufferFormat::BGR8:
|
|
||||||
fbuf->stride = m_framebuffer_width * 3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGBX8:
|
|
||||||
case FramebufferFormat::BGRX8:
|
|
||||||
fbuf->stride = m_framebuffer_width * 4;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGB565:
|
|
||||||
case FramebufferFormat::RGB555:
|
|
||||||
case FramebufferFormat::BGR555:
|
|
||||||
case FramebufferFormat::BGR565:
|
|
||||||
fbuf->stride = m_framebuffer_width * 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::C8RGBX8:
|
|
||||||
fbuf->stride = m_framebuffer_width;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
fbuf->data = new byte[fbuf->stride * m_framebuffer_height];
|
|
||||||
Y_memzero(fbuf->data, fbuf->stride * m_framebuffer_height);
|
|
||||||
|
|
||||||
if (IsPaletteFormat(m_framebuffer_format))
|
|
||||||
{
|
|
||||||
fbuf->palette = new u32[PALETTE_SIZE];
|
|
||||||
Y_memzero(fbuf->palette, sizeof(u32) * PALETTE_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::DestroyFramebuffer(Framebuffer* fbuf)
|
|
||||||
{
|
|
||||||
delete[] fbuf->palette;
|
|
||||||
fbuf->palette = nullptr;
|
|
||||||
|
|
||||||
delete[] fbuf->data;
|
|
||||||
fbuf->data = nullptr;
|
|
||||||
fbuf->width = 0;
|
|
||||||
fbuf->height = 0;
|
|
||||||
fbuf->stride = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::ResizeFramebuffer(u32 width, u32 height)
|
|
||||||
{
|
|
||||||
if (m_framebuffer_width == width && m_framebuffer_height == height)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_framebuffer_width = width;
|
|
||||||
m_framebuffer_height = height;
|
|
||||||
AllocateFramebuffer(&m_back_buffers[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::ChangeFramebufferFormat(FramebufferFormat new_format)
|
|
||||||
{
|
|
||||||
if (m_framebuffer_format == new_format)
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_framebuffer_format = new_format;
|
|
||||||
AllocateFramebuffer(&m_back_buffers[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SetPixel(u32 x, u32 y, u8 r, u8 g, u8 b)
|
|
||||||
{
|
|
||||||
SetPixel(x, y, PackRGBX(r, g, b));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::SetPixel(u32 x, u32 y, u32 rgb)
|
|
||||||
{
|
|
||||||
DebugAssert(x < m_framebuffer_width && y < m_framebuffer_height);
|
|
||||||
|
|
||||||
// Assumes LE order in rgb and framebuffer.
|
|
||||||
switch (m_framebuffer_format)
|
|
||||||
{
|
|
||||||
case FramebufferFormat::RGB8:
|
|
||||||
case FramebufferFormat::BGR8:
|
|
||||||
std::memcpy(&m_back_buffers[0].data[y * m_back_buffers[0].stride + x * 3], &rgb, 3);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGBX8:
|
|
||||||
case FramebufferFormat::BGRX8:
|
|
||||||
rgb |= 0xFF000000;
|
|
||||||
std::memcpy(&m_back_buffers[0].data[y * m_back_buffers[0].stride + x * 4], &rgb, 4);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGB555:
|
|
||||||
case FramebufferFormat::BGR555:
|
|
||||||
rgb &= 0x7FFF;
|
|
||||||
std::memcpy(&m_back_buffers[0].data[y * m_back_buffers[0].stride + x * 2], &rgb, 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGB565:
|
|
||||||
case FramebufferFormat::BGR565:
|
|
||||||
std::memcpy(&m_back_buffers[0].data[y * m_back_buffers[0].stride + x * 2], &rgb, 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::C8RGBX8:
|
|
||||||
m_back_buffers[0].data[y * m_back_buffers[0].stride + x] = Truncate8(rgb);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::CopyToFramebuffer(const void* pixels, u32 stride)
|
|
||||||
{
|
|
||||||
if (stride == m_back_buffers[0].stride)
|
|
||||||
{
|
|
||||||
std::memcpy(m_back_buffers[0].data, pixels, stride * m_framebuffer_height);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const byte* pixels_src = reinterpret_cast<const byte*>(pixels);
|
|
||||||
byte* pixels_dst = m_back_buffers[0].data;
|
|
||||||
u32 copy_stride = std::min(m_back_buffers[0].stride, stride);
|
|
||||||
for (u32 i = 0; i < m_framebuffer_height; i++)
|
|
||||||
{
|
|
||||||
std::memcpy(pixels_dst, pixels_src, copy_stride);
|
|
||||||
pixels_src += stride;
|
|
||||||
pixels_dst += m_back_buffers[0].stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::RepeatFrame()
|
|
||||||
{
|
|
||||||
// Don't change the framebuffer.
|
|
||||||
AddFrameRendered();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::CopyPalette(u8 start_index, u32 num_entries, const u32* entries)
|
|
||||||
{
|
|
||||||
DebugAssert(IsPaletteFormat(m_framebuffer_format) && (ZeroExtend32(start_index) + num_entries) <= PALETTE_SIZE);
|
|
||||||
std::copy_n(entries, num_entries, &m_back_buffers[0].palette[start_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::AddFrameRendered()
|
|
||||||
{
|
|
||||||
m_frames_rendered++;
|
|
||||||
|
|
||||||
// Update every 500ms
|
|
||||||
float dt = float(m_frame_counter_timer.GetTimeSeconds());
|
|
||||||
if (dt >= 1.0f)
|
|
||||||
{
|
|
||||||
m_fps = float(m_frames_rendered) * (1.0f / dt);
|
|
||||||
m_frames_rendered = 0;
|
|
||||||
m_frame_counter_timer.Reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline u32 ConvertRGB555ToRGBX8888(u16 color)
|
|
||||||
{
|
|
||||||
u8 r = Truncate8(color & 31);
|
|
||||||
u8 g = Truncate8((color >> 5) & 31);
|
|
||||||
u8 b = Truncate8((color >> 10) & 31);
|
|
||||||
|
|
||||||
// 00012345 -> 1234545
|
|
||||||
b = (b << 3) | (b >> 3);
|
|
||||||
g = (g << 3) | (g >> 3);
|
|
||||||
r = (r << 3) | (r >> 3);
|
|
||||||
|
|
||||||
return UINT32_C(0xFF000000) | ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline u32 ConvertRGB565ToRGBX8888(u16 color)
|
|
||||||
{
|
|
||||||
u8 r = Truncate8(color & 31);
|
|
||||||
u8 g = Truncate8((color >> 5) & 63);
|
|
||||||
u8 b = Truncate8((color >> 11) & 31);
|
|
||||||
|
|
||||||
// 00012345 -> 1234545 / 00123456 -> 12345656
|
|
||||||
r = (r << 3) | (r >> 3);
|
|
||||||
g = (g << 2) | (g >> 4);
|
|
||||||
b = (b << 3) | (b >> 3);
|
|
||||||
|
|
||||||
return UINT32_C(0xFF000000) | ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline u32 ConvertBGR555ToRGBX8888(u16 color)
|
|
||||||
{
|
|
||||||
u8 b = Truncate8(color & 31);
|
|
||||||
u8 g = Truncate8((color >> 5) & 31);
|
|
||||||
u8 r = Truncate8((color >> 10) & 31);
|
|
||||||
|
|
||||||
// 00012345 -> 1234545
|
|
||||||
b = (b << 3) | (b >> 3);
|
|
||||||
g = (g << 3) | (g >> 3);
|
|
||||||
r = (r << 3) | (r >> 3);
|
|
||||||
|
|
||||||
return UINT32_C(0xFF000000) | ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline u32 ConvertBGR565ToRGBX8888(u16 color)
|
|
||||||
{
|
|
||||||
u8 b = Truncate8(color & 31);
|
|
||||||
u8 g = Truncate8((color >> 5) & 63);
|
|
||||||
u8 r = Truncate8((color >> 11) & 31);
|
|
||||||
|
|
||||||
// 00012345 -> 1234545 / 00123456 -> 12345656
|
|
||||||
b = (b << 3) | (b >> 3);
|
|
||||||
g = (g << 2) | (g >> 4);
|
|
||||||
r = (r << 3) | (r >> 3);
|
|
||||||
|
|
||||||
return UINT32_C(0xFF000000) | ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Display::CopyFramebufferToRGBA8Buffer(const Framebuffer* fbuf, void* dst, u32 dst_stride)
|
|
||||||
{
|
|
||||||
const byte* src_ptr = reinterpret_cast<const byte*>(fbuf->data);
|
|
||||||
byte* dst_ptr = reinterpret_cast<byte*>(dst);
|
|
||||||
|
|
||||||
switch (fbuf->format)
|
|
||||||
{
|
|
||||||
case FramebufferFormat::RGB8:
|
|
||||||
{
|
|
||||||
// yuck.. TODO optimize this, using vectorization?
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u32 ocol = 0xFF000000;
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)); // R
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)) << 8; // G
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)) << 16; // B
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGBX8:
|
|
||||||
{
|
|
||||||
const u32 copy_size = std::min(fbuf->stride, dst_stride);
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
std::memcpy(dst_ptr, src_ptr, copy_size);
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::BGR8:
|
|
||||||
{
|
|
||||||
// yuck.. TODO optimize this, using vectorization?
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u32 ocol = 0xFF000000;
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)) << 16; // B
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)) << 8; // G
|
|
||||||
ocol |= ZeroExtend32(*(src_row_ptr++)); // R
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::BGRX8:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const u32* row_src_ptr = reinterpret_cast<const u32*>(src_ptr);
|
|
||||||
u32* row_dst_ptr = reinterpret_cast<u32*>(dst_ptr);
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
const u32 pix = *(row_src_ptr++);
|
|
||||||
*(row_dst_ptr++) =
|
|
||||||
(pix & UINT32_C(0xFF00FF00)) | ((pix & UINT32_C(0xFF)) << 16) | ((pix >> 16) & UINT32_C(0xFF));
|
|
||||||
}
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGB555:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u16 icol;
|
|
||||||
std::memcpy(&icol, src_row_ptr, sizeof(icol));
|
|
||||||
src_row_ptr += sizeof(icol);
|
|
||||||
u32 ocol = ConvertRGB555ToRGBX8888(icol);
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::RGB565:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u16 icol;
|
|
||||||
std::memcpy(&icol, src_row_ptr, sizeof(icol));
|
|
||||||
src_row_ptr += sizeof(icol);
|
|
||||||
u32 ocol = ConvertRGB565ToRGBX8888(icol);
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::BGR555:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u16 icol;
|
|
||||||
std::memcpy(&icol, src_row_ptr, sizeof(icol));
|
|
||||||
src_row_ptr += sizeof(icol);
|
|
||||||
u32 ocol = ConvertBGR555ToRGBX8888(icol);
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::BGR565:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
u16 icol;
|
|
||||||
std::memcpy(&icol, src_row_ptr, sizeof(icol));
|
|
||||||
src_row_ptr += sizeof(icol);
|
|
||||||
u32 ocol = ConvertBGR565ToRGBX8888(icol);
|
|
||||||
std::memcpy(dst_row_ptr, &ocol, sizeof(ocol));
|
|
||||||
dst_row_ptr += sizeof(ocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FramebufferFormat::C8RGBX8:
|
|
||||||
{
|
|
||||||
for (u32 row = 0; row < fbuf->height; row++)
|
|
||||||
{
|
|
||||||
const byte* src_row_ptr = src_ptr;
|
|
||||||
byte* dst_row_ptr = dst_ptr;
|
|
||||||
for (u32 col = 0; col < fbuf->width; col++)
|
|
||||||
{
|
|
||||||
std::memcpy(dst_row_ptr, &fbuf->palette[ZeroExtend32(*src_row_ptr++)], sizeof(u32));
|
|
||||||
dst_row_ptr += sizeof(u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
src_ptr += fbuf->stride;
|
|
||||||
dst_ptr += dst_stride;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,146 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "YBaseLib/Common.h"
|
|
||||||
#include "YBaseLib/String.h"
|
|
||||||
#include "YBaseLib/Timer.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
class DisplayRenderer;
|
|
||||||
|
|
||||||
class Display
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum : u8
|
|
||||||
{
|
|
||||||
// Priority for primary display. The display with the highest priority will be shown.
|
|
||||||
DEFAULT_PRIORITY = 1
|
|
||||||
};
|
|
||||||
enum : u32
|
|
||||||
{
|
|
||||||
// Number of colours in paletted modes.
|
|
||||||
PALETTE_SIZE = 256
|
|
||||||
};
|
|
||||||
enum class Type : u8
|
|
||||||
{
|
|
||||||
Primary,
|
|
||||||
Secondary
|
|
||||||
};
|
|
||||||
enum class FramebufferFormat : u8
|
|
||||||
{
|
|
||||||
RGB8,
|
|
||||||
RGBX8,
|
|
||||||
BGR8,
|
|
||||||
BGRX8,
|
|
||||||
RGB565,
|
|
||||||
RGB555,
|
|
||||||
BGR565,
|
|
||||||
BGR555,
|
|
||||||
C8RGBX8, // 8-bit palette, 32-bit colours
|
|
||||||
};
|
|
||||||
|
|
||||||
Display(DisplayRenderer* renderer, const String& name, Type type, u8 priority);
|
|
||||||
virtual ~Display();
|
|
||||||
|
|
||||||
const String& GetName() const { return m_name; }
|
|
||||||
Type GetType() const { return m_type; }
|
|
||||||
u8 GetPriority() const { return m_priority; }
|
|
||||||
|
|
||||||
bool IsEnabled() const { return m_enabled; }
|
|
||||||
bool IsActive() const { return m_active; }
|
|
||||||
void SetEnable(bool enabled);
|
|
||||||
void SetActive(bool active);
|
|
||||||
|
|
||||||
u32 GetFramesRendered() const { return m_frames_rendered; }
|
|
||||||
float GetFramesPerSecond() const { return m_fps; }
|
|
||||||
void ResetFramesRendered() { m_frames_rendered = 0; }
|
|
||||||
|
|
||||||
u32 GetDisplayWidth() const { return m_display_width; }
|
|
||||||
u32 GetDisplayHeight() const { return m_display_height; }
|
|
||||||
void SetDisplayScale(u32 scale) { m_display_scale = scale; }
|
|
||||||
void SetDisplayAspectRatio(u32 numerator, u32 denominator);
|
|
||||||
void ResizeDisplay(u32 width = 0, u32 height = 0);
|
|
||||||
|
|
||||||
u32 GetFramebufferWidth() const { return m_framebuffer_width; }
|
|
||||||
u32 GetFramebufferHeight() const { return m_framebuffer_height; }
|
|
||||||
FramebufferFormat GetFramebufferFormat() const { return m_framebuffer_format; }
|
|
||||||
|
|
||||||
void ClearFramebuffer();
|
|
||||||
void ResizeFramebuffer(u32 width, u32 height);
|
|
||||||
void ChangeFramebufferFormat(FramebufferFormat new_format);
|
|
||||||
void SwapFramebuffer();
|
|
||||||
|
|
||||||
static constexpr u32 PackRGBX(u8 r, u8 g, u8 b)
|
|
||||||
{
|
|
||||||
return (static_cast<u32>(r) << 0) | (static_cast<u32>(g) << 8) | (static_cast<u32>(b) << 16) |
|
|
||||||
(static_cast<u32>(0xFF) << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes pixels in the backbuffer.
|
|
||||||
byte* GetFramebufferPointer() const { return m_back_buffers[0].data; }
|
|
||||||
u32 GetFramebufferStride() const { return m_back_buffers[0].stride; }
|
|
||||||
void SetPixel(u32 x, u32 y, u8 r, u8 g, u8 b);
|
|
||||||
void SetPixel(u32 x, u32 y, u32 rgb);
|
|
||||||
void CopyToFramebuffer(const void* pixels, u32 stride);
|
|
||||||
void RepeatFrame();
|
|
||||||
|
|
||||||
// Update palette.
|
|
||||||
const u32* GetPalettePointer() const { return m_back_buffers[0].palette; }
|
|
||||||
void SetPaletteEntry(u8 index, u32 value) const { m_back_buffers[0].palette[index] = value; }
|
|
||||||
void CopyPalette(u8 start_index, u32 num_entries, const u32* entries);
|
|
||||||
|
|
||||||
// Returns true if the specified format is a paletted format.
|
|
||||||
static constexpr bool IsPaletteFormat(FramebufferFormat format) { return (format == FramebufferFormat::C8RGBX8); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static constexpr u32 NUM_BACK_BUFFERS = 2;
|
|
||||||
|
|
||||||
struct Framebuffer
|
|
||||||
{
|
|
||||||
byte* data = nullptr;
|
|
||||||
u32* palette = nullptr;
|
|
||||||
u32 width = 0;
|
|
||||||
u32 height = 0;
|
|
||||||
u32 stride = 0;
|
|
||||||
FramebufferFormat format = FramebufferFormat::RGBX8;
|
|
||||||
bool dirty = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AddFrameRendered();
|
|
||||||
void AllocateFramebuffer(Framebuffer* fbuf);
|
|
||||||
void DestroyFramebuffer(Framebuffer* fbuf);
|
|
||||||
|
|
||||||
// Updates the front buffer. Returns false if the no swap has occurred.
|
|
||||||
bool UpdateFrontbuffer();
|
|
||||||
|
|
||||||
// Helper for converting/copying a framebuffer.
|
|
||||||
static void CopyFramebufferToRGBA8Buffer(const Framebuffer* fbuf, void* dst, u32 dst_stride);
|
|
||||||
|
|
||||||
DisplayRenderer* m_renderer;
|
|
||||||
String m_name;
|
|
||||||
|
|
||||||
Type m_type;
|
|
||||||
u8 m_priority;
|
|
||||||
|
|
||||||
u32 m_framebuffer_width = 0;
|
|
||||||
u32 m_framebuffer_height = 0;
|
|
||||||
FramebufferFormat m_framebuffer_format = FramebufferFormat::RGBX8;
|
|
||||||
|
|
||||||
Framebuffer m_front_buffer;
|
|
||||||
Framebuffer m_back_buffers[NUM_BACK_BUFFERS];
|
|
||||||
std::mutex m_buffer_lock;
|
|
||||||
|
|
||||||
u32 m_display_width = 640;
|
|
||||||
u32 m_display_height = 480;
|
|
||||||
u32 m_display_scale = 1;
|
|
||||||
u32 m_display_aspect_numerator = 1;
|
|
||||||
u32 m_display_aspect_denominator = 1;
|
|
||||||
|
|
||||||
static constexpr u32 FRAME_COUNTER_FRAME_COUNT = 100;
|
|
||||||
Timer m_frame_counter_timer;
|
|
||||||
u32 m_frames_rendered = 0;
|
|
||||||
float m_fps = 0.0f;
|
|
||||||
|
|
||||||
bool m_enabled = true;
|
|
||||||
bool m_active = true;
|
|
||||||
};
|
|
|
@ -1,196 +0,0 @@
|
||||||
#include "display_renderer.h"
|
|
||||||
#include "display_renderer_d3d.h"
|
|
||||||
#include "display_renderer_gl.h"
|
|
||||||
|
|
||||||
DisplayRenderer::DisplayRenderer(WindowHandleType window_handle, u32 window_width, u32 window_height)
|
|
||||||
: m_window_handle(window_handle), m_window_width(window_width), m_window_height(window_height)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayRenderer::~DisplayRenderer()
|
|
||||||
{
|
|
||||||
Assert(m_primary_displays.empty());
|
|
||||||
Assert(m_secondary_displays.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
float DisplayRenderer::GetPrimaryDisplayFramesPerSecond()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
if (m_active_displays.empty())
|
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
return m_active_displays.front()->GetFramesPerSecond();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRenderer::Initialize()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::AddDisplay(Display* display)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
|
|
||||||
if (display->GetType() == Display::Type::Primary)
|
|
||||||
m_primary_displays.push_back(display);
|
|
||||||
else
|
|
||||||
m_secondary_displays.push_back(display);
|
|
||||||
|
|
||||||
UpdateActiveDisplays();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::RemoveDisplay(Display* display)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
|
|
||||||
auto& container = (display->GetType() == Display::Type::Primary) ? m_primary_displays : m_secondary_displays;
|
|
||||||
auto iter = std::find(container.begin(), container.end(), display);
|
|
||||||
if (iter != container.end())
|
|
||||||
container.erase(iter);
|
|
||||||
|
|
||||||
UpdateActiveDisplays();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::DisplayEnabled(Display* display)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
UpdateActiveDisplays();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::DisplayDisabled(Display* display)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
UpdateActiveDisplays();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::DisplayResized(Display* display) {}
|
|
||||||
|
|
||||||
void DisplayRenderer::DisplayFramebufferSwapped(Display* display) {}
|
|
||||||
|
|
||||||
void DisplayRenderer::WindowResized(u32 window_width, u32 window_height)
|
|
||||||
{
|
|
||||||
m_window_width = window_width;
|
|
||||||
m_window_height = window_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRenderer::UpdateActiveDisplays()
|
|
||||||
{
|
|
||||||
m_active_displays.clear();
|
|
||||||
|
|
||||||
// Find the primary display with the highest priority, and enabled.
|
|
||||||
Display* primary_display = nullptr;
|
|
||||||
for (Display* dpy : m_primary_displays)
|
|
||||||
{
|
|
||||||
dpy->SetActive(false);
|
|
||||||
if (dpy->IsEnabled() && (!primary_display || dpy->GetPriority() > primary_display->GetPriority()))
|
|
||||||
primary_display = dpy;
|
|
||||||
}
|
|
||||||
if (primary_display)
|
|
||||||
{
|
|
||||||
primary_display->SetActive(true);
|
|
||||||
m_active_displays.push_back(primary_display);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all enabled secondary displays.
|
|
||||||
for (Display* dpy : m_secondary_displays)
|
|
||||||
{
|
|
||||||
if (dpy->IsEnabled())
|
|
||||||
{
|
|
||||||
dpy->SetActive(true);
|
|
||||||
m_active_displays.push_back(dpy);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dpy->SetActive(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<u32, u32> DisplayRenderer::GetDisplayRenderSize(const Display* display)
|
|
||||||
{
|
|
||||||
Assert(!m_active_displays.empty());
|
|
||||||
const u32 window_width = m_window_width / u32(m_active_displays.size());
|
|
||||||
const u32 window_height = u32(std::max(1, int(m_window_height) - int(m_top_padding)));
|
|
||||||
const float display_ratio = float(display->GetDisplayWidth()) / float(display->GetDisplayHeight());
|
|
||||||
const float window_ratio = float(window_width) / float(window_height);
|
|
||||||
u32 viewport_width;
|
|
||||||
u32 viewport_height;
|
|
||||||
if (window_ratio >= display_ratio)
|
|
||||||
{
|
|
||||||
viewport_width = u32(float(window_height) * display_ratio);
|
|
||||||
viewport_height = u32(window_height);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
viewport_width = u32(window_width);
|
|
||||||
viewport_height = u32(float(window_width) / display_ratio);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_pair(viewport_width, viewport_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class DisplayRendererNull final : public DisplayRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayRendererNull(WindowHandleType window_handle, u32 window_width, u32 window_height)
|
|
||||||
: DisplayRenderer(window_handle, window_width, window_height)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
BackendType GetBackendType() override { return DisplayRenderer::BackendType::Null; }
|
|
||||||
|
|
||||||
virtual std::unique_ptr<Display> CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority = Display::DEFAULT_PRIORITY) override
|
|
||||||
{
|
|
||||||
auto display = std::make_unique<Display>(this, name, type, priority);
|
|
||||||
AddDisplay(display.get());
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool BeginFrame() override { return true; }
|
|
||||||
|
|
||||||
virtual void RenderDisplays() override {}
|
|
||||||
|
|
||||||
virtual void EndFrame() override {}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
DisplayRenderer::BackendType DisplayRenderer::GetDefaultBackendType()
|
|
||||||
{
|
|
||||||
#ifdef Y_COMPILER_MSVC
|
|
||||||
return BackendType::Direct3D;
|
|
||||||
#else
|
|
||||||
return BackendType::OpenGL;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<DisplayRenderer> DisplayRenderer::Create(BackendType backend, WindowHandleType window_handle,
|
|
||||||
u32 window_width, u32 window_height)
|
|
||||||
{
|
|
||||||
std::unique_ptr<DisplayRenderer> renderer;
|
|
||||||
switch (backend)
|
|
||||||
{
|
|
||||||
case BackendType::Null:
|
|
||||||
renderer = std::make_unique<DisplayRendererNull>(window_handle, window_width, window_height);
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef Y_COMPILER_MSVC
|
|
||||||
case BackendType::Direct3D:
|
|
||||||
renderer = std::make_unique<DisplayRendererD3D>(window_handle, window_width, window_height);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case BackendType::OpenGL:
|
|
||||||
renderer = std::make_unique<DisplayRendererGL>(window_handle, window_width, window_height);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderer->Initialize())
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
return renderer;
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "display.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class DisplayRenderer;
|
|
||||||
|
|
||||||
class DisplayRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using WindowHandleType = void*;
|
|
||||||
enum class BackendType
|
|
||||||
{
|
|
||||||
Null,
|
|
||||||
Direct3D,
|
|
||||||
OpenGL
|
|
||||||
};
|
|
||||||
|
|
||||||
DisplayRenderer(WindowHandleType window_handle, u32 window_width, u32 window_height);
|
|
||||||
virtual ~DisplayRenderer();
|
|
||||||
|
|
||||||
u32 GetWindowWidth() const { return m_window_width; }
|
|
||||||
u32 GetWindowHeight() const { return m_window_height; }
|
|
||||||
|
|
||||||
u32 GetTopPadding() const { return m_top_padding; }
|
|
||||||
void SetTopPadding(u32 padding) { m_top_padding = padding; }
|
|
||||||
|
|
||||||
float GetPrimaryDisplayFramesPerSecond();
|
|
||||||
|
|
||||||
virtual BackendType GetBackendType() = 0;
|
|
||||||
|
|
||||||
virtual std::unique_ptr<Display> CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority = Display::DEFAULT_PRIORITY) = 0;
|
|
||||||
|
|
||||||
virtual void RemoveDisplay(Display* display);
|
|
||||||
|
|
||||||
virtual void DisplayEnabled(Display* display);
|
|
||||||
virtual void DisplayDisabled(Display* display);
|
|
||||||
virtual void DisplayResized(Display* display);
|
|
||||||
virtual void DisplayFramebufferSwapped(Display* display);
|
|
||||||
|
|
||||||
virtual void WindowResized(u32 window_width, u32 window_height);
|
|
||||||
|
|
||||||
virtual bool BeginFrame() = 0;
|
|
||||||
virtual void RenderDisplays() = 0;
|
|
||||||
virtual void EndFrame() = 0;
|
|
||||||
|
|
||||||
/// Returns the default backend type for the system.
|
|
||||||
static BackendType GetDefaultBackendType();
|
|
||||||
|
|
||||||
static std::unique_ptr<DisplayRenderer> Create(BackendType backend, WindowHandleType window_handle, u32 window_width,
|
|
||||||
u32 window_height);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool Initialize();
|
|
||||||
|
|
||||||
void AddDisplay(Display* display);
|
|
||||||
void UpdateActiveDisplays();
|
|
||||||
|
|
||||||
std::pair<u32, u32> GetDisplayRenderSize(const Display* display);
|
|
||||||
|
|
||||||
WindowHandleType m_window_handle;
|
|
||||||
u32 m_window_width;
|
|
||||||
u32 m_window_height;
|
|
||||||
u32 m_top_padding = 0;
|
|
||||||
|
|
||||||
std::vector<Display*> m_primary_displays;
|
|
||||||
std::vector<Display*> m_secondary_displays;
|
|
||||||
std::vector<Display*> m_active_displays;
|
|
||||||
std::mutex m_display_lock;
|
|
||||||
};
|
|
|
@ -1,340 +0,0 @@
|
||||||
#include "display_renderer_d3d.h"
|
|
||||||
#include "YBaseLib/Assert.h"
|
|
||||||
#include "YBaseLib/Memory.h"
|
|
||||||
#include "YBaseLib/String.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#if defined(Y_COMPILER_MSVC)
|
|
||||||
#pragma comment(lib, "d3d11.lib")
|
|
||||||
|
|
||||||
static constexpr u32 SWAP_CHAIN_BUFFER_COUNT = 2;
|
|
||||||
static constexpr DXGI_FORMAT SWAP_CHAIN_BUFFER_FORMAT = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
||||||
|
|
||||||
static constexpr u32 VS_BYTECODE[] = {
|
|
||||||
0x43425844, 0x0608a44a, 0xd0e1754a, 0xec57c233, 0x42017a39, 0x00000001, 0x000002a8, 0x00000005, 0x00000034,
|
|
||||||
0x00000080, 0x000000b4, 0x0000010c, 0x0000022c, 0x46454452, 0x00000044, 0x00000000, 0x00000000, 0x00000000,
|
|
||||||
0x0000001c, 0xfffe0400, 0x00000100, 0x0000001c, 0x7263694d, 0x666f736f, 0x52282074, 0x4c482029, 0x53204c53,
|
|
||||||
0x65646168, 0x6f432072, 0x6c69706d, 0x31207265, 0x00312e30, 0x4e475349, 0x0000002c, 0x00000001, 0x00000008,
|
|
||||||
0x00000020, 0x00000000, 0x00000006, 0x00000001, 0x00000000, 0x00000101, 0x565f5653, 0x65747265, 0x00444978,
|
|
||||||
0x4e47534f, 0x00000050, 0x00000002, 0x00000008, 0x00000038, 0x00000000, 0x00000000, 0x00000003, 0x00000000,
|
|
||||||
0x00000c03, 0x00000041, 0x00000000, 0x00000001, 0x00000003, 0x00000001, 0x0000000f, 0x43584554, 0x44524f4f,
|
|
||||||
0x5f565300, 0x69736f50, 0x6e6f6974, 0xababab00, 0x52444853, 0x00000118, 0x00010040, 0x00000046, 0x04000060,
|
|
||||||
0x00101012, 0x00000000, 0x00000006, 0x03000065, 0x00102032, 0x00000000, 0x04000067, 0x001020f2, 0x00000001,
|
|
||||||
0x00000001, 0x02000068, 0x00000001, 0x07000029, 0x00100012, 0x00000000, 0x0010100a, 0x00000000, 0x00004001,
|
|
||||||
0x00000001, 0x07000001, 0x00100012, 0x00000000, 0x0010000a, 0x00000000, 0x00004001, 0x00000002, 0x07000001,
|
|
||||||
0x00100042, 0x00000000, 0x0010100a, 0x00000000, 0x00004001, 0x00000002, 0x05000056, 0x00100032, 0x00000000,
|
|
||||||
0x00100086, 0x00000000, 0x05000036, 0x00102032, 0x00000000, 0x00100046, 0x00000000, 0x0f000032, 0x00102032,
|
|
||||||
0x00000001, 0x00100046, 0x00000000, 0x00004002, 0x40000000, 0xc0000000, 0x00000000, 0x00000000, 0x00004002,
|
|
||||||
0xbf800000, 0x3f800000, 0x00000000, 0x00000000, 0x08000036, 0x001020c2, 0x00000001, 0x00004002, 0x00000000,
|
|
||||||
0x00000000, 0x00000000, 0x3f800000, 0x0100003e, 0x54415453, 0x00000074, 0x00000008, 0x00000001, 0x00000000,
|
|
||||||
0x00000003, 0x00000001, 0x00000001, 0x00000002, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
|
||||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000002, 0x00000000,
|
|
||||||
0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
|
|
||||||
|
|
||||||
static constexpr u32 PS_BYTECODE[] = {
|
|
||||||
0x43425844, 0x76fb5edf, 0x6680f045, 0x1a81341f, 0xac4335f9, 0x00000001, 0x0000021c, 0x00000005, 0x00000034,
|
|
||||||
0x000000cc, 0x00000100, 0x00000134, 0x000001a0, 0x46454452, 0x00000090, 0x00000000, 0x00000000, 0x00000002,
|
|
||||||
0x0000001c, 0xffff0400, 0x00000100, 0x00000067, 0x0000005c, 0x00000003, 0x00000000, 0x00000000, 0x00000000,
|
|
||||||
0x00000000, 0x00000001, 0x00000000, 0x00000062, 0x00000002, 0x00000005, 0x00000004, 0xffffffff, 0x00000000,
|
|
||||||
0x00000001, 0x0000000c, 0x706d6173, 0x65740030, 0x4d003078, 0x6f726369, 0x74666f73, 0x29522820, 0x534c4820,
|
|
||||||
0x6853204c, 0x72656461, 0x6d6f4320, 0x656c6970, 0x30312072, 0xab00312e, 0x4e475349, 0x0000002c, 0x00000001,
|
|
||||||
0x00000008, 0x00000020, 0x00000000, 0x00000000, 0x00000003, 0x00000000, 0x00000303, 0x43584554, 0x44524f4f,
|
|
||||||
0xababab00, 0x4e47534f, 0x0000002c, 0x00000001, 0x00000008, 0x00000020, 0x00000000, 0x00000000, 0x00000003,
|
|
||||||
0x00000000, 0x0000000f, 0x545f5653, 0x65677261, 0xabab0074, 0x52444853, 0x00000064, 0x00000040, 0x00000019,
|
|
||||||
0x0300005a, 0x00106000, 0x00000000, 0x04001858, 0x00107000, 0x00000000, 0x00005555, 0x03001062, 0x00101032,
|
|
||||||
0x00000000, 0x03000065, 0x001020f2, 0x00000000, 0x09000045, 0x001020f2, 0x00000000, 0x00101046, 0x00000000,
|
|
||||||
0x00107e46, 0x00000000, 0x00106000, 0x00000000, 0x0100003e, 0x54415453, 0x00000074, 0x00000002, 0x00000000,
|
|
||||||
0x00000000, 0x00000002, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
|
|
||||||
0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
|
||||||
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class DisplayGL : public Display
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority);
|
|
||||||
~DisplayGL();
|
|
||||||
|
|
||||||
void Render();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void UpdateFramebufferTexture();
|
|
||||||
void UpdateSamplerState();
|
|
||||||
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11SamplerState> m_sampler_state = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11Texture2D> m_framebuffer_texture = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_framebuffer_texture_srv = nullptr;
|
|
||||||
|
|
||||||
u32 m_framebuffer_texture_width = 0;
|
|
||||||
u32 m_framebuffer_texture_height = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
DisplayGL::DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority)
|
|
||||||
: Display(display_manager, name, type, priority)
|
|
||||||
{
|
|
||||||
// TODO: Customizable sampler states
|
|
||||||
UpdateSamplerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayGL::~DisplayGL() = default;
|
|
||||||
|
|
||||||
void DisplayGL::Render()
|
|
||||||
{
|
|
||||||
if (UpdateFrontbuffer())
|
|
||||||
UpdateFramebufferTexture();
|
|
||||||
|
|
||||||
if (!m_framebuffer_texture || !m_sampler_state)
|
|
||||||
return;
|
|
||||||
|
|
||||||
DisplayRendererD3D* dm = static_cast<DisplayRendererD3D*>(m_renderer);
|
|
||||||
ID3D11DeviceContext* d3d_context = dm->GetD3DContext();
|
|
||||||
|
|
||||||
d3d_context->RSSetState(dm->GetD3DRasterizerState());
|
|
||||||
d3d_context->OMSetDepthStencilState(dm->GetD3DDepthState(), 0);
|
|
||||||
d3d_context->OMSetBlendState(dm->GetD3DBlendState(), nullptr, 0xFFFFFFFF);
|
|
||||||
|
|
||||||
d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
||||||
d3d_context->VSSetShader(dm->GetD3DVertexShader(), nullptr, 0);
|
|
||||||
d3d_context->PSSetShader(dm->GetD3DPixelShader(), nullptr, 0);
|
|
||||||
d3d_context->PSSetShaderResources(0, 1, m_framebuffer_texture_srv.GetAddressOf());
|
|
||||||
d3d_context->PSSetSamplers(0, 1, m_sampler_state.GetAddressOf());
|
|
||||||
|
|
||||||
d3d_context->Draw(3, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayGL::UpdateFramebufferTexture()
|
|
||||||
{
|
|
||||||
ID3D11Device* d3d_device = static_cast<DisplayRendererD3D*>(m_renderer)->GetD3DDevice();
|
|
||||||
ID3D11DeviceContext* d3d_context = static_cast<DisplayRendererD3D*>(m_renderer)->GetD3DContext();
|
|
||||||
|
|
||||||
if (m_framebuffer_texture_width != m_front_buffer.width || m_framebuffer_texture_height != m_front_buffer.height)
|
|
||||||
{
|
|
||||||
m_framebuffer_texture_width = m_front_buffer.width;
|
|
||||||
m_framebuffer_texture_height = m_front_buffer.height;
|
|
||||||
m_framebuffer_texture.Reset();
|
|
||||||
m_framebuffer_texture_srv.Reset();
|
|
||||||
|
|
||||||
if (m_framebuffer_texture_width > 0 && m_framebuffer_texture_height > 0)
|
|
||||||
{
|
|
||||||
D3D11_TEXTURE2D_DESC desc =
|
|
||||||
CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R8G8B8A8_UNORM, m_framebuffer_texture_width, m_framebuffer_texture_height, 1,
|
|
||||||
1, D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
|
|
||||||
HRESULT hr = d3d_device->CreateTexture2D(&desc, nullptr, m_framebuffer_texture.ReleaseAndGetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
|
||||||
Panic("Failed to create framebuffer texture.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
|
|
||||||
srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
||||||
srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
|
||||||
srv_desc.Texture2D.MostDetailedMip = 0;
|
|
||||||
srv_desc.Texture2D.MipLevels = 1;
|
|
||||||
hr = d3d_device->CreateShaderResourceView(m_framebuffer_texture.Get(), &srv_desc,
|
|
||||||
m_framebuffer_texture_srv.ReleaseAndGetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
|
||||||
Panic("Failed to create framebuffer texture SRV.");
|
|
||||||
m_framebuffer_texture.Reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_framebuffer_texture)
|
|
||||||
return;
|
|
||||||
|
|
||||||
D3D11_MAPPED_SUBRESOURCE sr;
|
|
||||||
HRESULT hr = d3d_context->Map(m_framebuffer_texture.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &sr);
|
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
|
||||||
Panic("Failed to map framebuffer texture.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CopyFramebufferToRGBA8Buffer(&m_front_buffer, sr.pData, sr.RowPitch);
|
|
||||||
|
|
||||||
d3d_context->Unmap(m_framebuffer_texture.Get(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayGL::UpdateSamplerState()
|
|
||||||
{
|
|
||||||
ID3D11Device* d3d_device = static_cast<DisplayRendererD3D*>(m_renderer)->GetD3DDevice();
|
|
||||||
|
|
||||||
m_sampler_state.Reset();
|
|
||||||
|
|
||||||
D3D11_SAMPLER_DESC ss_desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT());
|
|
||||||
ss_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
|
||||||
// ss_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
|
||||||
HRESULT hr = d3d_device->CreateSamplerState(&ss_desc, m_sampler_state.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
{
|
|
||||||
Panic("Failed to create sampler state");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
DisplayRendererD3D::DisplayRendererD3D(WindowHandleType window_handle, u32 window_width, u32 window_height)
|
|
||||||
: DisplayRenderer(window_handle, window_width, window_height)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayRendererD3D::~DisplayRendererD3D() = default;
|
|
||||||
|
|
||||||
std::unique_ptr<Display> DisplayRendererD3D::CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority /*= Display::DEFAULT_PRIORITY*/)
|
|
||||||
{
|
|
||||||
std::unique_ptr<DisplayGL> display = std::make_unique<DisplayGL>(this, name, type, priority);
|
|
||||||
AddDisplay(display.get());
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererD3D::Initialize()
|
|
||||||
{
|
|
||||||
if (!DisplayRenderer::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
DXGI_SWAP_CHAIN_DESC desc = {};
|
|
||||||
desc.BufferDesc.Format = SWAP_CHAIN_BUFFER_FORMAT;
|
|
||||||
desc.BufferUsage = DXGI_USAGE_BACK_BUFFER | DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
||||||
desc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;
|
|
||||||
desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
|
||||||
desc.SampleDesc.Count = 1;
|
|
||||||
desc.SampleDesc.Quality = 0;
|
|
||||||
desc.Windowed = TRUE;
|
|
||||||
desc.OutputWindow = static_cast<HWND>(m_window_handle);
|
|
||||||
|
|
||||||
D3D_FEATURE_LEVEL feature_level;
|
|
||||||
HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, nullptr, 0, D3D11_SDK_VERSION,
|
|
||||||
&desc, m_swap_chain.GetAddressOf(), m_device.GetAddressOf(),
|
|
||||||
&feature_level, m_context.GetAddressOf());
|
|
||||||
if (FAILED(hr) || feature_level < D3D_FEATURE_LEVEL_10_0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Disable DXGI responding to ALT+ENTER, we need to capture these keystrokes and handle it ourselves.
|
|
||||||
Microsoft::WRL::ComPtr<IDXGIFactory> dxgi_factory;
|
|
||||||
hr = m_swap_chain->GetParent(IID_PPV_ARGS(dxgi_factory.GetAddressOf()));
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
hr = dxgi_factory->MakeWindowAssociation(desc.OutputWindow, DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER |
|
|
||||||
DXGI_MWA_NO_PRINT_SCREEN);
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!CreateRenderTargetView())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
hr = m_device->CreateVertexShader(VS_BYTECODE, sizeof(VS_BYTECODE), nullptr, m_vertex_shader.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
hr = m_device->CreatePixelShader(PS_BYTECODE, sizeof(PS_BYTECODE), nullptr, m_pixel_shader.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
D3D11_RASTERIZER_DESC rs_desc = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT());
|
|
||||||
hr = m_device->CreateRasterizerState(&rs_desc, m_rasterizer_state.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
D3D11_DEPTH_STENCIL_DESC ds_desc = CD3D11_DEPTH_STENCIL_DESC(CD3D11_DEFAULT());
|
|
||||||
ds_desc.DepthEnable = FALSE;
|
|
||||||
ds_desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
|
|
||||||
hr = m_device->CreateDepthStencilState(&ds_desc, m_depth_state.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
D3D11_BLEND_DESC bs_desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT());
|
|
||||||
hr = m_device->CreateBlendState(&bs_desc, m_blend_state.GetAddressOf());
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererD3D::BeginFrame()
|
|
||||||
{
|
|
||||||
std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
|
||||||
m_context->ClearRenderTargetView(m_swap_chain_rtv.Get(), clear_color.data());
|
|
||||||
m_context->OMSetRenderTargets(1, m_swap_chain_rtv.GetAddressOf(), nullptr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererD3D::RenderDisplays()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
|
|
||||||
// How many pixels do we need to render?
|
|
||||||
u32 total_width = 0;
|
|
||||||
for (const Display* display : m_active_displays)
|
|
||||||
{
|
|
||||||
auto dim = GetDisplayRenderSize(display);
|
|
||||||
total_width += dim.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the viewport bounds.
|
|
||||||
const int window_width = int(m_window_width);
|
|
||||||
const int window_height = std::max(1, int(m_window_height) - int(m_top_padding));
|
|
||||||
|
|
||||||
int viewport_x = (m_window_width - total_width) / 2;
|
|
||||||
for (Display* display : m_active_displays)
|
|
||||||
{
|
|
||||||
auto dim = GetDisplayRenderSize(display);
|
|
||||||
const int viewport_width = int(dim.first);
|
|
||||||
const int viewport_height = int(dim.second);
|
|
||||||
const int viewport_y = ((window_height - viewport_height) / 2) + m_top_padding;
|
|
||||||
|
|
||||||
D3D11_VIEWPORT vp = { float(viewport_x), float(viewport_y), float(viewport_width), float(viewport_height), 0.0f, 1.0f };
|
|
||||||
m_context->RSSetViewports(1, &vp);
|
|
||||||
|
|
||||||
static_cast<DisplayGL*>(display)->Render();
|
|
||||||
|
|
||||||
viewport_x += dim.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererD3D::EndFrame()
|
|
||||||
{
|
|
||||||
m_swap_chain->Present(1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererD3D::WindowResized(u32 window_width, u32 window_height)
|
|
||||||
{
|
|
||||||
DisplayRenderer::WindowResized(window_width, window_height);
|
|
||||||
|
|
||||||
m_context->OMSetRenderTargets(0, nullptr, nullptr);
|
|
||||||
m_swap_chain_rtv.Reset();
|
|
||||||
|
|
||||||
HRESULT hr =
|
|
||||||
m_swap_chain->ResizeBuffers(SWAP_CHAIN_BUFFER_COUNT, m_window_width, m_window_height, SWAP_CHAIN_BUFFER_FORMAT, 0);
|
|
||||||
if (FAILED(hr) || !CreateRenderTargetView())
|
|
||||||
Panic("Failed to resize swap chain buffers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererD3D::CreateRenderTargetView()
|
|
||||||
{
|
|
||||||
D3D11_RENDER_TARGET_VIEW_DESC desc = {};
|
|
||||||
desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
|
|
||||||
desc.Format = SWAP_CHAIN_BUFFER_FORMAT;
|
|
||||||
desc.Texture2D.MipSlice = 0;
|
|
||||||
|
|
||||||
ID3D11Texture2D* surface;
|
|
||||||
HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(&surface));
|
|
||||||
if (FAILED(hr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
hr = m_device->CreateRenderTargetView(surface, &desc, m_swap_chain_rtv.GetAddressOf());
|
|
||||||
surface->Release();
|
|
||||||
return SUCCEEDED(hr);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayRenderer::BackendType DisplayRendererD3D::GetBackendType()
|
|
||||||
{
|
|
||||||
return DisplayRenderer::BackendType::Direct3D;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,55 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "YBaseLib/Common.h"
|
|
||||||
|
|
||||||
#if defined(Y_COMPILER_MSVC)
|
|
||||||
|
|
||||||
#include "YBaseLib/Windows/WindowsHeaders.h"
|
|
||||||
#include "display_renderer.h"
|
|
||||||
#include <d3d11.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <wrl.h>
|
|
||||||
|
|
||||||
class DisplayRendererD3D final : public DisplayRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayRendererD3D(WindowHandleType window_handle, u32 window_width, u32 window_height);
|
|
||||||
~DisplayRendererD3D();
|
|
||||||
|
|
||||||
BackendType GetBackendType() override;
|
|
||||||
|
|
||||||
std::unique_ptr<Display> CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority = Display::DEFAULT_PRIORITY) override;
|
|
||||||
|
|
||||||
void WindowResized(u32 window_width, u32 window_height) override;
|
|
||||||
|
|
||||||
bool BeginFrame() override;
|
|
||||||
void RenderDisplays() override;
|
|
||||||
void EndFrame() override;
|
|
||||||
|
|
||||||
ID3D11Device* GetD3DDevice() const { return m_device.Get(); }
|
|
||||||
ID3D11DeviceContext* GetD3DContext() const { return m_context.Get(); }
|
|
||||||
ID3D11VertexShader* GetD3DVertexShader() const { return m_vertex_shader.Get(); }
|
|
||||||
ID3D11PixelShader* GetD3DPixelShader() const { return m_pixel_shader.Get(); }
|
|
||||||
ID3D11RasterizerState* GetD3DRasterizerState() const { return m_rasterizer_state.Get(); }
|
|
||||||
ID3D11DepthStencilState* GetD3DDepthState() const { return m_depth_state.Get(); }
|
|
||||||
ID3D11BlendState* GetD3DBlendState() const { return m_blend_state.Get(); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool Initialize() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool CreateRenderTargetView();
|
|
||||||
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11Device> m_device = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11DeviceContext> m_context = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<IDXGISwapChain> m_swap_chain = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> m_swap_chain_rtv = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11VertexShader> m_vertex_shader = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11PixelShader> m_pixel_shader = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11RasterizerState> m_rasterizer_state = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11DepthStencilState> m_depth_state = nullptr;
|
|
||||||
Microsoft::WRL::ComPtr<ID3D11BlendState> m_blend_state = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,367 +0,0 @@
|
||||||
#include "display_renderer_gl.h"
|
|
||||||
#include "YBaseLib/Assert.h"
|
|
||||||
#include "YBaseLib/Memory.h"
|
|
||||||
#include "YBaseLib/String.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <glad.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class DisplayGL : public Display
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority);
|
|
||||||
~DisplayGL();
|
|
||||||
|
|
||||||
void Render();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void UpdateFramebufferTexture();
|
|
||||||
|
|
||||||
GLuint m_framebuffer_texture_id = 0;
|
|
||||||
|
|
||||||
u32 m_framebuffer_texture_width = 0;
|
|
||||||
u32 m_framebuffer_texture_height = 0;
|
|
||||||
|
|
||||||
std::vector<byte> m_framebuffer_texture_upload_buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
DisplayGL::DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority)
|
|
||||||
: Display(display_manager, name, type, priority)
|
|
||||||
{
|
|
||||||
// TODO: Customizable sampler states
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayGL::~DisplayGL()
|
|
||||||
{
|
|
||||||
if (m_framebuffer_texture_id != 0)
|
|
||||||
glDeleteTextures(1, &m_framebuffer_texture_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayGL::Render()
|
|
||||||
{
|
|
||||||
if (UpdateFrontbuffer())
|
|
||||||
UpdateFramebufferTexture();
|
|
||||||
|
|
||||||
if (m_framebuffer_texture_id == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Assumes that everything is already setup/bound.
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
|
||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayGL::UpdateFramebufferTexture()
|
|
||||||
{
|
|
||||||
if (m_framebuffer_texture_width != m_front_buffer.width || m_framebuffer_texture_height != m_front_buffer.height)
|
|
||||||
{
|
|
||||||
m_framebuffer_texture_width = m_front_buffer.width;
|
|
||||||
m_framebuffer_texture_height = m_front_buffer.height;
|
|
||||||
|
|
||||||
if (m_framebuffer_texture_width > 0 && m_framebuffer_texture_height > 0)
|
|
||||||
{
|
|
||||||
if (m_framebuffer_texture_id == 0)
|
|
||||||
glGenTextures(1, &m_framebuffer_texture_id);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_framebuffer_texture_width, m_framebuffer_texture_height, 0, GL_RGBA,
|
|
||||||
GL_UNSIGNED_BYTE, nullptr);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
||||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (m_framebuffer_texture_id != 0)
|
|
||||||
{
|
|
||||||
glDeleteTextures(1, &m_framebuffer_texture_id);
|
|
||||||
m_framebuffer_texture_id = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_framebuffer_texture_id == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const u32 upload_stride = m_framebuffer_texture_width * sizeof(u32);
|
|
||||||
const size_t required_bytes = size_t(upload_stride) * m_framebuffer_texture_height;
|
|
||||||
if (m_framebuffer_texture_upload_buffer.size() != required_bytes)
|
|
||||||
m_framebuffer_texture_upload_buffer.resize(required_bytes);
|
|
||||||
|
|
||||||
CopyFramebufferToRGBA8Buffer(&m_front_buffer, m_framebuffer_texture_upload_buffer.data(), upload_stride);
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_framebuffer_texture_width, m_framebuffer_texture_height, GL_RGBA,
|
|
||||||
GL_UNSIGNED_BYTE, m_framebuffer_texture_upload_buffer.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
DisplayRendererGL::DisplayRendererGL(WindowHandleType window_handle, u32 window_width, u32 window_height)
|
|
||||||
: DisplayRenderer(window_handle, window_width, window_height)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayRendererGL::~DisplayRendererGL() = default;
|
|
||||||
|
|
||||||
std::unique_ptr<Display> DisplayRendererGL::CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority /*= Display::DEFAULT_PRIORITY*/)
|
|
||||||
{
|
|
||||||
std::unique_ptr<DisplayGL> display = std::make_unique<DisplayGL>(this, name, type, priority);
|
|
||||||
AddDisplay(display.get());
|
|
||||||
return display;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererGL::Initialize()
|
|
||||||
{
|
|
||||||
if (!DisplayRenderer::Initialize())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!GLAD_GL_VERSION_2_0)
|
|
||||||
{
|
|
||||||
Panic("GL version 2.0 not loaded.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreateQuadVAO())
|
|
||||||
{
|
|
||||||
Panic("Failed to create quad VAO");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CreateQuadProgram())
|
|
||||||
{
|
|
||||||
Panic("Failed to create quad program");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererGL::BeginFrame()
|
|
||||||
{
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererGL::RenderDisplays()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
|
||||||
|
|
||||||
// Setup GL state.
|
|
||||||
glDisable(GL_SCISSOR_TEST);
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glUseProgram(m_quad_program_id);
|
|
||||||
BindQuadVAO();
|
|
||||||
|
|
||||||
// How many pixels do we need to render?
|
|
||||||
u32 total_width = 0;
|
|
||||||
for (const Display* display : m_active_displays)
|
|
||||||
{
|
|
||||||
auto dim = GetDisplayRenderSize(display);
|
|
||||||
total_width += dim.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the viewport bounds.
|
|
||||||
const int window_width = int(m_window_width);
|
|
||||||
const int window_height = std::max(1, int(m_window_height) - int(m_top_padding));
|
|
||||||
|
|
||||||
int viewport_x = (window_width - total_width) / 2;
|
|
||||||
for (Display* display : m_active_displays)
|
|
||||||
{
|
|
||||||
auto dim = GetDisplayRenderSize(display);
|
|
||||||
const int viewport_width = int(dim.first);
|
|
||||||
const int viewport_height = int(dim.second);
|
|
||||||
const int viewport_y = ((window_height - viewport_height) / 2) + m_top_padding;
|
|
||||||
glViewport(viewport_x, m_window_height - viewport_height - viewport_y, viewport_width, viewport_height);
|
|
||||||
static_cast<DisplayGL*>(display)->Render();
|
|
||||||
viewport_x += dim.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererGL::EndFrame() {}
|
|
||||||
|
|
||||||
void DisplayRendererGL::WindowResized(u32 window_width, u32 window_height)
|
|
||||||
{
|
|
||||||
DisplayRenderer::WindowResized(window_width, window_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayRenderer::BackendType DisplayRendererGL::GetBackendType()
|
|
||||||
{
|
|
||||||
return DisplayRenderer::BackendType::OpenGL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct QuadVertex
|
|
||||||
{
|
|
||||||
float position[4];
|
|
||||||
float texcoord[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
bool DisplayRendererGL::CreateQuadVAO()
|
|
||||||
{
|
|
||||||
static const QuadVertex vertices[4] = {{{1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
|
|
||||||
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
|
|
||||||
{{1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
|
||||||
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}};
|
|
||||||
|
|
||||||
if (GLAD_GL_VERSION_3_0 || GLAD_GL_ES_VERSION_3_0)
|
|
||||||
{
|
|
||||||
glGenVertexArrays(1, &m_quad_vao_id);
|
|
||||||
glBindVertexArray(m_quad_vao_id);
|
|
||||||
glGenBuffers(1, &m_quad_vbo_id);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
||||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(QuadVertex, position[0])));
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(QuadVertex, texcoord[0])));
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
glGenBuffers(1, &m_quad_vbo_id);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayRendererGL::BindQuadVAO()
|
|
||||||
{
|
|
||||||
if (GLAD_GL_VERSION_3_0 || GLAD_GL_ES_VERSION_3_0)
|
|
||||||
{
|
|
||||||
glBindVertexArray(m_quad_vao_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// old-style
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
|
||||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(QuadVertex, position[0])));
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(QuadVertex, texcoord[0])));
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string GenerateQuadVertexShader(const bool old_glsl)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
if (old_glsl)
|
|
||||||
{
|
|
||||||
ss << "#version 110\n";
|
|
||||||
ss << "attribute vec4 a_position;\n";
|
|
||||||
ss << "attribute vec2 a_texcoord;\n";
|
|
||||||
ss << "varying vec2 v_texcoord;\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss << "#version 130\n";
|
|
||||||
ss << "in vec4 a_position;\n";
|
|
||||||
ss << "in vec2 a_texcoord;\n";
|
|
||||||
ss << "out vec2 v_texcoord;\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "void main() {\n";
|
|
||||||
ss << " gl_Position = a_position;\n";
|
|
||||||
ss << " v_texcoord = a_texcoord;\n";
|
|
||||||
ss << "}\n";
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string GenerateQuadFragmentShader(const bool old_glsl)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
if (old_glsl)
|
|
||||||
{
|
|
||||||
ss << "#version 110\n";
|
|
||||||
ss << "varying vec2 v_texcoord;\n";
|
|
||||||
ss << "uniform sampler2D samp0;\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss << "#version 130\n";
|
|
||||||
ss << "in vec2 v_texcoord;\n";
|
|
||||||
ss << "uniform sampler2D samp0;\n";
|
|
||||||
ss << "out vec4 ocol0;\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
ss << "void main() {\n";
|
|
||||||
if (old_glsl)
|
|
||||||
ss << " gl_FragColor = texture2D(samp0, v_texcoord);\n";
|
|
||||||
else
|
|
||||||
ss << " ocol0 = texture(samp0, v_texcoord);\n";
|
|
||||||
ss << "}\n";
|
|
||||||
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayRendererGL::CreateQuadProgram()
|
|
||||||
{
|
|
||||||
const bool old_glsl = !GLAD_GL_VERSION_3_2 && !GLAD_GL_ES_VERSION_3_0;
|
|
||||||
const std::string vs_str = GenerateQuadVertexShader(old_glsl);
|
|
||||||
const std::string fs_str = GenerateQuadFragmentShader(old_glsl);
|
|
||||||
const char* vs_str_ptr = vs_str.c_str();
|
|
||||||
const GLint vs_length = static_cast<GLint>(vs_str.length());
|
|
||||||
const char* fs_str_ptr = fs_str.c_str();
|
|
||||||
const GLint fs_length = static_cast<GLint>(fs_str.length());
|
|
||||||
GLint param;
|
|
||||||
|
|
||||||
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
|
||||||
glShaderSource(vs, 1, &vs_str_ptr, &vs_length);
|
|
||||||
glCompileShader(vs);
|
|
||||||
glGetShaderiv(vs, GL_COMPILE_STATUS, ¶m);
|
|
||||||
if (param != GL_TRUE)
|
|
||||||
{
|
|
||||||
Panic("Failed to compile vertex shader.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
|
||||||
glShaderSource(fs, 1, &fs_str_ptr, &fs_length);
|
|
||||||
glCompileShader(fs);
|
|
||||||
glGetShaderiv(fs, GL_COMPILE_STATUS, ¶m);
|
|
||||||
if (param != GL_TRUE)
|
|
||||||
{
|
|
||||||
Panic("Failed to compile fragment shader.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_quad_program_id = glCreateProgram();
|
|
||||||
glAttachShader(m_quad_program_id, vs);
|
|
||||||
glAttachShader(m_quad_program_id, fs);
|
|
||||||
glBindAttribLocation(m_quad_program_id, 0, "a_position");
|
|
||||||
glBindAttribLocation(m_quad_program_id, 1, "a_texcoord");
|
|
||||||
if (!old_glsl)
|
|
||||||
glBindFragDataLocation(m_quad_program_id, 0, "ocol0");
|
|
||||||
glLinkProgram(m_quad_program_id);
|
|
||||||
glGetProgramiv(m_quad_program_id, GL_LINK_STATUS, ¶m);
|
|
||||||
if (param != GL_TRUE)
|
|
||||||
{
|
|
||||||
Panic("Failed to link program.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind texture unit zero to the shader.
|
|
||||||
glUseProgram(m_quad_program_id);
|
|
||||||
GLint pos = glGetUniformLocation(m_quad_program_id, "samp0");
|
|
||||||
if (pos >= 0)
|
|
||||||
glUniform1i(pos, 0);
|
|
||||||
|
|
||||||
// Shaders are no longer needed after linking.
|
|
||||||
glDeleteShader(vs);
|
|
||||||
glDeleteShader(fs);
|
|
||||||
|
|
||||||
glUseProgram(0);
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "display_renderer.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
class DisplayRendererGL final : public DisplayRenderer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayRendererGL(WindowHandleType window_handle, u32 window_width, u32 window_height);
|
|
||||||
~DisplayRendererGL();
|
|
||||||
|
|
||||||
BackendType GetBackendType() override;
|
|
||||||
|
|
||||||
std::unique_ptr<Display> CreateDisplay(const char* name, Display::Type type,
|
|
||||||
u8 priority = Display::DEFAULT_PRIORITY) override;
|
|
||||||
|
|
||||||
void WindowResized(u32 window_width, u32 window_height) override;
|
|
||||||
|
|
||||||
bool BeginFrame() override;
|
|
||||||
void RenderDisplays() override;
|
|
||||||
void EndFrame() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool Initialize() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool CreateQuadVAO();
|
|
||||||
void BindQuadVAO();
|
|
||||||
|
|
||||||
bool CreateQuadProgram();
|
|
||||||
|
|
||||||
u32 m_quad_vbo_id = 0;
|
|
||||||
u32 m_quad_vao_id = 0;
|
|
||||||
u32 m_quad_program_id = 0;
|
|
||||||
};
|
|
|
@ -1,374 +0,0 @@
|
||||||
#include "display_timing.h"
|
|
||||||
#include "YBaseLib/String.h"
|
|
||||||
#include "common/state_wrapper.h"
|
|
||||||
|
|
||||||
DisplayTiming::DisplayTiming() = default;
|
|
||||||
|
|
||||||
void DisplayTiming::ResetClock(SimulationTime start_time)
|
|
||||||
{
|
|
||||||
m_clock_start_time = start_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
SimulationTime DisplayTiming::GetTime(SimulationTime time) const
|
|
||||||
{
|
|
||||||
return GetSimulationTimeDifference(m_clock_start_time, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 DisplayTiming::GetTimeInFrame(SimulationTime time) const
|
|
||||||
{
|
|
||||||
return static_cast<s32>(GetTime(time) % m_vertical_total_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetPixelClock(double clock)
|
|
||||||
{
|
|
||||||
m_pixel_clock = clock;
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetHorizontalVisible(s32 visible)
|
|
||||||
{
|
|
||||||
m_horizontal_visible = visible;
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetHorizontalSyncRange(s32 start, s32 end)
|
|
||||||
{
|
|
||||||
Assert(start <= end);
|
|
||||||
m_horizontal_front_porch = start - m_horizontal_visible;
|
|
||||||
m_horizontal_sync_length = end - start + 1;
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetHorizontalSyncLength(s32 start, s32 length)
|
|
||||||
{
|
|
||||||
m_horizontal_front_porch = start - m_horizontal_visible;
|
|
||||||
m_horizontal_sync_length = length;
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetHorizontalBackPorch(s32 bp)
|
|
||||||
{
|
|
||||||
m_horizontal_back_porch = bp;
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetHorizontalTotal(s32 total)
|
|
||||||
{
|
|
||||||
m_horizontal_back_porch = total - (m_horizontal_visible + m_horizontal_front_porch + m_horizontal_sync_length);
|
|
||||||
UpdateHorizontalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetVerticalVisible(s32 visible)
|
|
||||||
{
|
|
||||||
m_vertical_visible = visible;
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetVerticalSyncRange(s32 start, s32 end)
|
|
||||||
{
|
|
||||||
Assert(start <= end);
|
|
||||||
m_vertical_front_porch = start - m_vertical_visible;
|
|
||||||
m_vertical_sync_length = end - start + 1;
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetVerticalSyncLength(s32 start, s32 length)
|
|
||||||
{
|
|
||||||
m_vertical_front_porch = start - m_vertical_visible;
|
|
||||||
m_vertical_sync_length = length;
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetVerticalBackPorch(s32 bp)
|
|
||||||
{
|
|
||||||
m_vertical_back_porch = bp;
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::SetVerticalTotal(s32 total)
|
|
||||||
{
|
|
||||||
m_vertical_back_porch = total - (m_vertical_visible + m_vertical_front_porch + m_vertical_sync_length);
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayTiming::Snapshot DisplayTiming::GetSnapshot(SimulationTime time) const
|
|
||||||
{
|
|
||||||
Snapshot ss;
|
|
||||||
if (m_clock_enable && IsValid())
|
|
||||||
{
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
const s32 line_number = time_in_frame / m_horizontal_total_duration;
|
|
||||||
const s32 time_in_line = time_in_frame % m_horizontal_total_duration;
|
|
||||||
ss.current_line = static_cast<u32>(line_number);
|
|
||||||
ss.current_pixel = static_cast<u32>(time_in_line / m_horizontal_pixel_duration);
|
|
||||||
ss.in_vertical_blank = (time_in_frame >= m_vertical_active_duration);
|
|
||||||
ss.in_horizontal_blank = (!ss.in_vertical_blank && (time_in_line >= m_horizontal_sync_start_time &&
|
|
||||||
time_in_line < m_horizontal_sync_end_time));
|
|
||||||
ss.vsync_active = (time_in_frame >= m_vertical_sync_start_time && line_number < m_vertical_sync_end_time);
|
|
||||||
ss.hsync_active =
|
|
||||||
(!ss.vsync_active && (time_in_line >= m_horizontal_sync_start_time && time_in_line < m_horizontal_sync_end_time));
|
|
||||||
ss.display_active = !(ss.in_horizontal_blank | ss.in_vertical_blank);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ss.current_line = 0;
|
|
||||||
ss.current_pixel = 0;
|
|
||||||
ss.display_active = false;
|
|
||||||
ss.in_horizontal_blank = false;
|
|
||||||
ss.in_vertical_blank = false;
|
|
||||||
ss.hsync_active = false;
|
|
||||||
ss.vsync_active = false;
|
|
||||||
}
|
|
||||||
return ss;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::IsDisplayActive(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
return (time_in_frame < m_vertical_active_duration &&
|
|
||||||
(time_in_frame % m_horizontal_total_duration) < m_horizontal_active_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::InVerticalBlank(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
return (time_in_frame >= m_vertical_active_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::InHorizontalSync(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
if (time_in_frame >= m_vertical_sync_start_time && time_in_frame < m_vertical_sync_end_time)
|
|
||||||
{
|
|
||||||
// In vsync.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const s32 time_in_line = time_in_frame % m_horizontal_total_duration;
|
|
||||||
return (time_in_line >= m_horizontal_sync_start_time && time_in_frame < m_horizontal_sync_end_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::InVerticalSync(SimulationTime time) const
|
|
||||||
{
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
return (time_in_frame >= m_vertical_sync_start_time && time_in_frame < m_vertical_sync_end_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 DisplayTiming::GetCurrentLine(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
return static_cast<u32>(time_in_frame / m_horizontal_total_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
SimulationTime DisplayTiming::GetTimeUntilVSync(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
if (time_in_frame < m_vertical_sync_start_time)
|
|
||||||
return m_vertical_sync_start_time - time_in_frame;
|
|
||||||
else
|
|
||||||
return (m_vertical_total_duration - time_in_frame) + m_vertical_sync_start_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
SimulationTime DisplayTiming::GetTimeUntilVBlank(SimulationTime time) const
|
|
||||||
{
|
|
||||||
if (!m_clock_enable || !IsValid())
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
const s32 time_in_frame = GetTimeInFrame(time);
|
|
||||||
if (time_in_frame >= m_vertical_active_duration)
|
|
||||||
return ((m_vertical_total_duration - time_in_frame) + m_vertical_active_duration);
|
|
||||||
else
|
|
||||||
return (m_vertical_active_duration - time_in_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::ToString(String* str) const
|
|
||||||
{
|
|
||||||
const s32 horizontal_sync_start = m_horizontal_visible + m_horizontal_front_porch;
|
|
||||||
const s32 vertical_sync_start = m_vertical_visible + m_vertical_front_porch;
|
|
||||||
str->Format("%dx%d | %.3f KHz, %u Total, %d-%d Sync | %.3fhz, %d Total, %d-%d Sync", m_horizontal_visible,
|
|
||||||
m_vertical_visible, m_horizontal_frequency / 1000.0, m_horizontal_total, horizontal_sync_start,
|
|
||||||
horizontal_sync_start + m_horizontal_sync_length, m_vertical_frequency, m_vertical_total,
|
|
||||||
vertical_sync_start, vertical_sync_start + m_vertical_sync_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::FrequenciesMatch(const DisplayTiming& timing) const
|
|
||||||
{
|
|
||||||
return std::tie(m_pixel_clock, m_horizontal_visible, m_horizontal_front_porch, m_horizontal_sync_length,
|
|
||||||
m_horizontal_back_porch, m_horizontal_frequency, m_vertical_visible, m_vertical_front_porch,
|
|
||||||
m_vertical_sync_length, m_vertical_back_porch, m_vertical_frequency) ==
|
|
||||||
std::tie(timing.m_pixel_clock, timing.m_horizontal_visible, timing.m_horizontal_front_porch,
|
|
||||||
timing.m_horizontal_sync_length, timing.m_horizontal_back_porch, timing.m_horizontal_frequency,
|
|
||||||
timing.m_vertical_visible, timing.m_vertical_front_porch, timing.m_vertical_sync_length,
|
|
||||||
timing.m_vertical_back_porch, timing.m_vertical_frequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayTiming::DoState(StateWrapper& sw)
|
|
||||||
{
|
|
||||||
sw.Do(&m_clock_start_time);
|
|
||||||
sw.Do(&m_horizontal_visible);
|
|
||||||
sw.Do(&m_horizontal_front_porch);
|
|
||||||
sw.Do(&m_horizontal_sync_length);
|
|
||||||
sw.Do(&m_horizontal_back_porch);
|
|
||||||
sw.Do(&m_vertical_visible);
|
|
||||||
sw.Do(&m_vertical_front_porch);
|
|
||||||
sw.Do(&m_vertical_sync_length);
|
|
||||||
sw.Do(&m_vertical_back_porch);
|
|
||||||
sw.Do(&m_horizontal_total);
|
|
||||||
sw.Do(&m_vertical_total);
|
|
||||||
sw.Do(&m_pixel_clock);
|
|
||||||
sw.Do(&m_horizontal_frequency);
|
|
||||||
sw.Do(&m_vertical_frequency);
|
|
||||||
sw.Do(&m_horizontal_pixel_duration);
|
|
||||||
sw.Do(&m_horizontal_active_duration);
|
|
||||||
sw.Do(&m_horizontal_sync_start_time);
|
|
||||||
sw.Do(&m_horizontal_sync_end_time);
|
|
||||||
sw.Do(&m_horizontal_total_duration);
|
|
||||||
sw.Do(&m_vertical_active_duration);
|
|
||||||
sw.Do(&m_vertical_sync_start_time);
|
|
||||||
sw.Do(&m_vertical_sync_end_time);
|
|
||||||
sw.Do(&m_vertical_total_duration);
|
|
||||||
sw.Do(&m_clock_enable);
|
|
||||||
sw.Do(&m_valid);
|
|
||||||
return !sw.HasError();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::Reset()
|
|
||||||
{
|
|
||||||
m_clock_start_time = 0;
|
|
||||||
|
|
||||||
m_horizontal_visible = 0;
|
|
||||||
m_horizontal_front_porch = 0;
|
|
||||||
m_horizontal_sync_length = 0;
|
|
||||||
m_horizontal_back_porch = 0;
|
|
||||||
m_vertical_visible = 0;
|
|
||||||
m_vertical_front_porch = 0;
|
|
||||||
m_vertical_sync_length = 0;
|
|
||||||
m_vertical_back_porch = 0;
|
|
||||||
|
|
||||||
m_horizontal_total = 0;
|
|
||||||
m_vertical_total = 0;
|
|
||||||
|
|
||||||
m_pixel_clock = 0.0;
|
|
||||||
m_horizontal_frequency = 0.0f;
|
|
||||||
m_vertical_frequency = 0.0f;
|
|
||||||
|
|
||||||
m_horizontal_pixel_duration = 0;
|
|
||||||
m_horizontal_active_duration = 0;
|
|
||||||
m_horizontal_sync_start_time = 0;
|
|
||||||
m_horizontal_sync_end_time = 0;
|
|
||||||
m_horizontal_total_duration = 0;
|
|
||||||
m_vertical_active_duration = 0;
|
|
||||||
m_vertical_sync_start_time = 0;
|
|
||||||
m_vertical_sync_end_time = 0;
|
|
||||||
m_vertical_total_duration = 0;
|
|
||||||
|
|
||||||
m_clock_enable = false;
|
|
||||||
m_valid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayTiming& DisplayTiming::operator=(const DisplayTiming& timing)
|
|
||||||
{
|
|
||||||
m_clock_start_time = timing.m_clock_start_time;
|
|
||||||
m_horizontal_visible = timing.m_horizontal_visible;
|
|
||||||
m_horizontal_front_porch = timing.m_horizontal_front_porch;
|
|
||||||
m_horizontal_sync_length = timing.m_horizontal_sync_length;
|
|
||||||
m_horizontal_back_porch = timing.m_horizontal_back_porch;
|
|
||||||
m_vertical_visible = timing.m_vertical_visible;
|
|
||||||
m_vertical_front_porch = timing.m_vertical_front_porch;
|
|
||||||
m_vertical_sync_length = timing.m_vertical_sync_length;
|
|
||||||
m_vertical_back_porch = timing.m_vertical_back_porch;
|
|
||||||
m_horizontal_total = timing.m_horizontal_total;
|
|
||||||
m_vertical_total = timing.m_vertical_total;
|
|
||||||
m_pixel_clock = timing.m_pixel_clock;
|
|
||||||
m_horizontal_frequency = timing.m_horizontal_frequency;
|
|
||||||
m_vertical_frequency = timing.m_vertical_frequency;
|
|
||||||
m_horizontal_pixel_duration = timing.m_horizontal_pixel_duration;
|
|
||||||
m_horizontal_active_duration = timing.m_horizontal_active_duration;
|
|
||||||
m_horizontal_sync_start_time = timing.m_horizontal_sync_start_time;
|
|
||||||
m_horizontal_sync_end_time = timing.m_horizontal_sync_end_time;
|
|
||||||
m_horizontal_total_duration = timing.m_horizontal_total_duration;
|
|
||||||
m_vertical_active_duration = timing.m_vertical_active_duration;
|
|
||||||
m_vertical_sync_start_time = timing.m_vertical_sync_start_time;
|
|
||||||
m_vertical_sync_end_time = timing.m_vertical_sync_end_time;
|
|
||||||
m_vertical_total_duration = timing.m_vertical_total_duration;
|
|
||||||
m_clock_enable = timing.m_clock_enable;
|
|
||||||
m_valid = timing.m_valid;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::UpdateHorizontalFrequency()
|
|
||||||
{
|
|
||||||
if (m_pixel_clock == 0.0 || m_horizontal_visible <= 0 || m_horizontal_front_porch < 0 ||
|
|
||||||
m_horizontal_sync_length < 0 || m_horizontal_back_porch < 0)
|
|
||||||
{
|
|
||||||
m_horizontal_total = 0;
|
|
||||||
m_horizontal_frequency = 0.0;
|
|
||||||
m_horizontal_active_duration = 0;
|
|
||||||
m_horizontal_sync_start_time = 0;
|
|
||||||
m_horizontal_sync_end_time = 0;
|
|
||||||
m_horizontal_total_duration = 0;
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_horizontal_total =
|
|
||||||
m_horizontal_visible + m_horizontal_front_porch + m_horizontal_sync_length + m_horizontal_back_porch;
|
|
||||||
|
|
||||||
const double pixel_period = 1.0 / m_pixel_clock;
|
|
||||||
const double active_duration_s = pixel_period * static_cast<double>(m_horizontal_visible);
|
|
||||||
const double sync_start_time_s = pixel_period * static_cast<double>(m_horizontal_visible + m_horizontal_front_porch);
|
|
||||||
const double sync_end_time_s = sync_start_time_s + (pixel_period * static_cast<double>(m_horizontal_sync_length));
|
|
||||||
const double total_duration_s = pixel_period * static_cast<double>(m_horizontal_total);
|
|
||||||
|
|
||||||
m_horizontal_frequency = m_pixel_clock / static_cast<double>(m_horizontal_total);
|
|
||||||
m_horizontal_pixel_duration = static_cast<u32>(1000000000.0 * pixel_period);
|
|
||||||
m_horizontal_active_duration = static_cast<u32>(1000000000.0 * active_duration_s);
|
|
||||||
m_horizontal_sync_start_time = static_cast<u32>(1000000000.0 * sync_start_time_s);
|
|
||||||
m_horizontal_sync_end_time = static_cast<u32>(1000000000.0 * sync_end_time_s);
|
|
||||||
m_horizontal_total_duration = static_cast<u32>(1000000000.0 * total_duration_s);
|
|
||||||
UpdateVerticalFrequency();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::UpdateVerticalFrequency()
|
|
||||||
{
|
|
||||||
if (m_vertical_visible <= 0 || m_vertical_front_porch < 0 || m_vertical_sync_length < 0 || m_vertical_back_porch < 0)
|
|
||||||
{
|
|
||||||
m_vertical_total = 0;
|
|
||||||
m_vertical_frequency = 0;
|
|
||||||
m_vertical_active_duration = 0;
|
|
||||||
m_vertical_sync_start_time = 0;
|
|
||||||
m_vertical_sync_end_time = 0;
|
|
||||||
m_vertical_total_duration = 0;
|
|
||||||
UpdateValid();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_vertical_total = m_vertical_visible + m_vertical_front_porch + m_vertical_sync_length + m_vertical_back_porch;
|
|
||||||
m_vertical_frequency = m_horizontal_frequency / static_cast<double>(m_vertical_total);
|
|
||||||
m_vertical_active_duration = m_horizontal_total_duration * m_vertical_visible;
|
|
||||||
m_vertical_sync_start_time = m_horizontal_total_duration * (m_vertical_visible + m_vertical_front_porch);
|
|
||||||
m_vertical_sync_end_time = m_vertical_sync_start_time + (m_horizontal_total_duration * m_vertical_sync_length);
|
|
||||||
m_vertical_total_duration = m_horizontal_total_duration * m_vertical_total;
|
|
||||||
UpdateValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayTiming::UpdateValid()
|
|
||||||
{
|
|
||||||
m_valid = (m_horizontal_total_duration > 0 && m_vertical_total_duration > 0);
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
class StateWrapper;
|
|
||||||
class String;
|
|
||||||
|
|
||||||
class DisplayTiming
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DisplayTiming();
|
|
||||||
|
|
||||||
// H/V frequencies are valid?
|
|
||||||
bool IsValid() const { return m_valid; }
|
|
||||||
|
|
||||||
// Enables the clock at the specified start time.
|
|
||||||
bool IsClockEnabled() const { return m_clock_enable; }
|
|
||||||
void SetClockEnable(bool enable) { m_clock_enable = enable; }
|
|
||||||
void ResetClock(SimulationTime start_time);
|
|
||||||
|
|
||||||
// Returns the number of ticks since the clock was enabled.
|
|
||||||
SimulationTime GetTime(SimulationTime time) const;
|
|
||||||
|
|
||||||
// Returns the number of ticks elapsed in the current frame.
|
|
||||||
s32 GetTimeInFrame(SimulationTime time) const;
|
|
||||||
|
|
||||||
// Accessors.
|
|
||||||
s32 GetHorizontalVisible() const { return m_horizontal_visible; }
|
|
||||||
s32 GetHorizontalFrontPorch() const { return m_horizontal_front_porch; }
|
|
||||||
s32 GetHorizontalSyncLength() const { return m_horizontal_sync_length; }
|
|
||||||
s32 GetHorizontalBackPorch() const { return m_horizontal_back_porch; }
|
|
||||||
s32 GetHorizontalTotal() const { return m_horizontal_total; }
|
|
||||||
s32 GetVerticalVisible() const { return m_vertical_visible; }
|
|
||||||
s32 GetVerticalFrontPorch() const { return m_vertical_front_porch; }
|
|
||||||
s32 GetVerticalSyncLength() const { return m_vertical_sync_length; }
|
|
||||||
s32 GetVerticallBackPorch() const { return m_vertical_back_porch; }
|
|
||||||
s32 GetVerticalTotal() const { return m_vertical_total; }
|
|
||||||
double GetPixelClock() const { return m_pixel_clock; }
|
|
||||||
double GetHorizontalFrequency() const { return m_horizontal_frequency; }
|
|
||||||
double GetVerticalFrequency() const { return m_vertical_frequency; }
|
|
||||||
s32 GetHorizontalPixelDuration() const { return m_horizontal_pixel_duration; }
|
|
||||||
s32 GetHorizontalActiveDuration() const { return m_horizontal_active_duration; }
|
|
||||||
s32 GetHorizontalSyncStartTime() const { return m_horizontal_sync_start_time; }
|
|
||||||
s32 GetHorizontalSyncEndTime() const { return m_horizontal_sync_end_time; }
|
|
||||||
s32 GetHorizontalTotalDuration() const { return m_horizontal_total_duration; }
|
|
||||||
s32 GetHorizontalBlankStartTime() const { return m_horizontal_active_duration; }
|
|
||||||
s32 GetHorizontalBlankDuration() const { return m_horizontal_total_duration - m_horizontal_active_duration; }
|
|
||||||
s32 GetVerticalActiveDuration() const { return m_vertical_active_duration; }
|
|
||||||
s32 GetVerticalSyncStartTime() const { return m_vertical_sync_start_time; }
|
|
||||||
s32 GetVerticalSyncEndTime() const { return m_vertical_sync_end_time; }
|
|
||||||
s32 GetVerticalTotalDuration() const { return m_vertical_total_duration; }
|
|
||||||
s32 GetVerticalBlankStartTime() const { return m_vertical_active_duration; }
|
|
||||||
s32 GetVerticalBlankDuration() const { return m_vertical_total_duration - m_vertical_active_duration; }
|
|
||||||
|
|
||||||
// Setting horizontal timing based on pixels and clock.
|
|
||||||
void SetPixelClock(double clock);
|
|
||||||
void SetHorizontalVisible(s32 visible);
|
|
||||||
void SetHorizontalSyncRange(s32 start, s32 end);
|
|
||||||
void SetHorizontalSyncLength(s32 start, s32 length);
|
|
||||||
void SetHorizontalBackPorch(s32 bp);
|
|
||||||
void SetHorizontalTotal(s32 total);
|
|
||||||
void SetVerticalVisible(s32 visible);
|
|
||||||
void SetVerticalSyncRange(s32 start, s32 end);
|
|
||||||
void SetVerticalSyncLength(s32 start, s32 length);
|
|
||||||
void SetVerticalBackPorch(s32 bp);
|
|
||||||
void SetVerticalTotal(s32 total);
|
|
||||||
|
|
||||||
// Gets the timing state for the specified time point.
|
|
||||||
struct Snapshot
|
|
||||||
{
|
|
||||||
u32 current_line;
|
|
||||||
u32 current_pixel;
|
|
||||||
bool display_active; // visible part
|
|
||||||
bool in_horizontal_blank;
|
|
||||||
bool in_vertical_blank;
|
|
||||||
bool hsync_active;
|
|
||||||
bool vsync_active;
|
|
||||||
};
|
|
||||||
Snapshot GetSnapshot(SimulationTime time) const;
|
|
||||||
|
|
||||||
// Shorter versions of the above.
|
|
||||||
bool IsDisplayActive(SimulationTime time) const;
|
|
||||||
bool InVerticalBlank(SimulationTime time) const;
|
|
||||||
bool InHorizontalSync(SimulationTime time) const;
|
|
||||||
bool InVerticalSync(SimulationTime time) const;
|
|
||||||
u32 GetCurrentLine(SimulationTime time) const;
|
|
||||||
SimulationTime GetTimeUntilVSync(SimulationTime time) const;
|
|
||||||
|
|
||||||
// Returns the amount of time until the next vertical blank starts.
|
|
||||||
SimulationTime GetTimeUntilVBlank(SimulationTime time) const;
|
|
||||||
|
|
||||||
// Writes frequency information to the log.
|
|
||||||
void ToString(String* str) const;
|
|
||||||
|
|
||||||
// Tests whether frequencies and dimensions match.
|
|
||||||
bool FrequenciesMatch(const DisplayTiming& timing) const;
|
|
||||||
|
|
||||||
// Serialization.
|
|
||||||
bool DoState(StateWrapper& sw);
|
|
||||||
void Reset();
|
|
||||||
|
|
||||||
// Copy operator.
|
|
||||||
DisplayTiming& operator=(const DisplayTiming& timing);
|
|
||||||
|
|
||||||
// TODO: clock update to prevent wrap-around.
|
|
||||||
|
|
||||||
private:
|
|
||||||
void UpdateHorizontalFrequency();
|
|
||||||
void UpdateVerticalFrequency();
|
|
||||||
void UpdateValid();
|
|
||||||
|
|
||||||
SimulationTime m_clock_start_time = 0;
|
|
||||||
|
|
||||||
// Set
|
|
||||||
s32 m_horizontal_visible = 0;
|
|
||||||
s32 m_horizontal_front_porch = 0;
|
|
||||||
s32 m_horizontal_sync_length = 0;
|
|
||||||
s32 m_horizontal_back_porch = 0;
|
|
||||||
s32 m_vertical_visible = 0;
|
|
||||||
s32 m_vertical_front_porch = 0;
|
|
||||||
s32 m_vertical_sync_length = 0;
|
|
||||||
s32 m_vertical_back_porch = 0;
|
|
||||||
|
|
||||||
// Computed. End values are exclusive.
|
|
||||||
s32 m_horizontal_total = 0;
|
|
||||||
s32 m_vertical_total = 0;
|
|
||||||
|
|
||||||
double m_pixel_clock = 0.0;
|
|
||||||
double m_horizontal_frequency = 0.0f;
|
|
||||||
double m_vertical_frequency = 0.0f;
|
|
||||||
|
|
||||||
// TODO: Make these doubles?
|
|
||||||
s32 m_horizontal_pixel_duration = 0;
|
|
||||||
s32 m_horizontal_active_duration = 0;
|
|
||||||
s32 m_horizontal_sync_start_time = 0;
|
|
||||||
s32 m_horizontal_sync_end_time = 0;
|
|
||||||
s32 m_horizontal_total_duration = 0;
|
|
||||||
s32 m_vertical_active_duration = 0;
|
|
||||||
s32 m_vertical_sync_start_time = 0;
|
|
||||||
s32 m_vertical_sync_end_time = 0;
|
|
||||||
s32 m_vertical_total_duration = 0;
|
|
||||||
|
|
||||||
bool m_clock_enable = false;
|
|
||||||
bool m_valid = false;
|
|
||||||
};
|
|
|
@ -1,63 +0,0 @@
|
||||||
_TEXT SEGMENT
|
|
||||||
|
|
||||||
PUBLIC fastjmp_set
|
|
||||||
PUBLIC fastjmp_jmp
|
|
||||||
|
|
||||||
; void fastjmp_set(fastjmp_buf*)
|
|
||||||
fastjmp_set PROC
|
|
||||||
mov rax, qword ptr [rsp]
|
|
||||||
mov rdx, rsp ; fixup stack pointer, so it doesn't include the call to fastjmp_set
|
|
||||||
add rdx, 8
|
|
||||||
mov qword ptr [rcx], rax ; actually rip
|
|
||||||
mov qword ptr [rcx + 8], rbx
|
|
||||||
mov qword ptr [rcx + 16], rdx ; actually rsp
|
|
||||||
mov qword ptr [rcx + 24], rbp
|
|
||||||
mov qword ptr [rcx + 32], rsi
|
|
||||||
mov qword ptr [rcx + 40], rdi
|
|
||||||
mov qword ptr [rcx + 48], r12
|
|
||||||
mov qword ptr [rcx + 56], r13
|
|
||||||
mov qword ptr [rcx + 64], r14
|
|
||||||
mov qword ptr [rcx + 72], r15
|
|
||||||
movaps xmmword ptr [rcx + 80], xmm6
|
|
||||||
movaps xmmword ptr [rcx + 96], xmm7
|
|
||||||
movaps xmmword ptr [rcx + 112], xmm8
|
|
||||||
add rcx, 112 ; split to two batches to fit displacement in a single byte
|
|
||||||
movaps xmmword ptr [rcx + 16], xmm9
|
|
||||||
movaps xmmword ptr [rcx + 32], xmm10
|
|
||||||
movaps xmmword ptr [rcx + 48], xmm11
|
|
||||||
movaps xmmword ptr [rcx + 64], xmm12
|
|
||||||
movaps xmmword ptr [rcx + 80], xmm13
|
|
||||||
movaps xmmword ptr [rcx + 96], xmm14
|
|
||||||
movaps xmmword ptr [rcx + 112], xmm15
|
|
||||||
ret
|
|
||||||
fastjmp_set ENDP
|
|
||||||
|
|
||||||
; void fastjmp_jmp(fastjmp_buf*)
|
|
||||||
fastjmp_jmp PROC
|
|
||||||
mov rax, qword ptr [rcx + 0] ; actually rip
|
|
||||||
mov rbx, qword ptr [rcx + 8]
|
|
||||||
mov rsp, qword ptr [rcx + 16]
|
|
||||||
mov rbp, qword ptr [rcx + 24]
|
|
||||||
mov rsi, qword ptr [rcx + 32]
|
|
||||||
mov rdi, qword ptr [rcx + 40]
|
|
||||||
mov r12, qword ptr [rcx + 48]
|
|
||||||
mov r13, qword ptr [rcx + 56]
|
|
||||||
mov r14, qword ptr [rcx + 64]
|
|
||||||
mov r15, qword ptr [rcx + 72]
|
|
||||||
movaps xmm6, xmmword ptr [rcx + 80]
|
|
||||||
movaps xmm7, xmmword ptr [rcx + 96]
|
|
||||||
movaps xmm8, xmmword ptr [rcx + 112]
|
|
||||||
add rcx, 112 ; split to two batches to fit displacement in a single byte
|
|
||||||
movaps xmm9, xmmword ptr [rcx + 16]
|
|
||||||
movaps xmm10, xmmword ptr [rcx + 32]
|
|
||||||
movaps xmm11, xmmword ptr [rcx + 48]
|
|
||||||
movaps xmm12, xmmword ptr [rcx + 64]
|
|
||||||
movaps xmm13, xmmword ptr [rcx + 80]
|
|
||||||
movaps xmm14, xmmword ptr [rcx + 96]
|
|
||||||
movaps xmm15, xmmword ptr [rcx + 112]
|
|
||||||
jmp rax
|
|
||||||
fastjmp_jmp ENDP
|
|
||||||
|
|
||||||
_TEXT ENDS
|
|
||||||
|
|
||||||
END
|
|
|
@ -1,44 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
|
|
||||||
__declspec(align(16)) struct fastjmp_buf
|
|
||||||
{
|
|
||||||
unsigned __int64 Rip;
|
|
||||||
unsigned __int64 Rbx;
|
|
||||||
unsigned __int64 Rsp;
|
|
||||||
unsigned __int64 Rbp;
|
|
||||||
unsigned __int64 Rsi;
|
|
||||||
unsigned __int64 Rdi;
|
|
||||||
unsigned __int64 R12;
|
|
||||||
unsigned __int64 R13;
|
|
||||||
unsigned __int64 R14;
|
|
||||||
unsigned __int64 R15;
|
|
||||||
unsigned __int64 Xmm6[2];
|
|
||||||
unsigned __int64 Xmm7[2];
|
|
||||||
unsigned __int64 Xmm8[2];
|
|
||||||
unsigned __int64 Xmm9[2];
|
|
||||||
unsigned __int64 Xmm10[2];
|
|
||||||
unsigned __int64 Xmm11[2];
|
|
||||||
unsigned __int64 Xmm12[2];
|
|
||||||
unsigned __int64 Xmm13[2];
|
|
||||||
unsigned __int64 Xmm14[2];
|
|
||||||
unsigned __int64 Xmm15[2];
|
|
||||||
// unsigned long MxCsr;
|
|
||||||
// unsigned short FpCsr;
|
|
||||||
// unsigned short Spare;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
void fastjmp_set(fastjmp_buf*);
|
|
||||||
void fastjmp_jmp(fastjmp_buf*);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <setjmp.h>
|
|
||||||
#define fastjmp_buf jmp_buf
|
|
||||||
#define fastjmp_set(buf) setjmp(*(buf))
|
|
||||||
#define fastjmp_jmp(buf) longjmp(*(buf), 0)
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,566 +0,0 @@
|
||||||
#include "hdd_image.h"
|
|
||||||
#include "YBaseLib/FileSystem.h"
|
|
||||||
#include "YBaseLib/Log.h"
|
|
||||||
Log_SetChannel(HDDImage);
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
static constexpr u32 LOG_FILE_MAGIC = 0x89374897;
|
|
||||||
struct LOG_FILE_HEADER
|
|
||||||
{
|
|
||||||
u32 magic;
|
|
||||||
u32 sector_size;
|
|
||||||
u64 image_size;
|
|
||||||
u32 sector_count;
|
|
||||||
u32 version_number;
|
|
||||||
u8 padding[12];
|
|
||||||
};
|
|
||||||
static constexpr u32 STATE_MAGIC = 0x92087348;
|
|
||||||
struct STATE_HEADER
|
|
||||||
{
|
|
||||||
u32 magic;
|
|
||||||
u32 sector_size;
|
|
||||||
u64 image_size;
|
|
||||||
u32 sector_count;
|
|
||||||
u32 version_number;
|
|
||||||
u32 num_sectors_in_state;
|
|
||||||
u8 padding[8];
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
static String GetLogFileName(const char* base_filename)
|
|
||||||
{
|
|
||||||
return String::FromFormat("%s.log", base_filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static u64 GetSectorMapOffset(HDDImage::SectorIndex index)
|
|
||||||
{
|
|
||||||
return sizeof(LOG_FILE_HEADER) + (static_cast<u64>(index) * sizeof(HDDImage::SectorIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
HDDImage::HDDImage(const std::string filename, ByteStream* base_stream, ByteStream* log_stream, u64 size,
|
|
||||||
u32 sector_size, u32 sector_count, u32 version_number, LogSectorMap log_sector_map)
|
|
||||||
: m_filename(std::move(filename)), m_base_stream(base_stream), m_log_stream(log_stream), m_image_size(size),
|
|
||||||
m_sector_size(sector_size), m_sector_count(sector_count), m_version_number(version_number),
|
|
||||||
m_log_sector_map(std::move(log_sector_map))
|
|
||||||
{
|
|
||||||
m_current_sector.data = std::make_unique<byte[]>(sector_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
HDDImage::~HDDImage()
|
|
||||||
{
|
|
||||||
m_base_stream->Release();
|
|
||||||
m_log_stream->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteStream* HDDImage::CreateLogFile(const char* filename, bool truncate_existing, bool atomic_update, u64 image_size,
|
|
||||||
u32 sector_size, u32& num_sectors, u32 version_number, LogSectorMap& sector_map)
|
|
||||||
{
|
|
||||||
if (sector_size == 0 || !Common::IsPow2(sector_size) ||
|
|
||||||
((image_size + (sector_size - 1)) / sector_size) >= std::numeric_limits<SectorIndex>::max())
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill sector map with zeros.
|
|
||||||
num_sectors = static_cast<u32>((image_size + (sector_size - 1)) / sector_size);
|
|
||||||
sector_map.resize(static_cast<size_t>(num_sectors));
|
|
||||||
std::fill_n(sector_map.begin(), static_cast<size_t>(num_sectors), InvalidSectorNumber);
|
|
||||||
|
|
||||||
u32 open_flags = BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_SEEKABLE;
|
|
||||||
if (truncate_existing)
|
|
||||||
open_flags |= BYTESTREAM_OPEN_TRUNCATE;
|
|
||||||
if (atomic_update)
|
|
||||||
open_flags |= BYTESTREAM_OPEN_ATOMIC_UPDATE;
|
|
||||||
|
|
||||||
ByteStream* log_stream = FileSystem::OpenFile(filename, open_flags);
|
|
||||||
if (!log_stream)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
LOG_FILE_HEADER header = {};
|
|
||||||
header.magic = LOG_FILE_MAGIC;
|
|
||||||
header.sector_size = sector_size;
|
|
||||||
header.image_size = image_size;
|
|
||||||
header.sector_count = static_cast<u32>(num_sectors);
|
|
||||||
header.version_number = version_number;
|
|
||||||
|
|
||||||
// Write header and sector map to the file.
|
|
||||||
if (!log_stream->Write2(&header, sizeof(header)) ||
|
|
||||||
!log_stream->Write2(sector_map.data(), static_cast<u32>(sizeof(SectorIndex) * sector_map.size())))
|
|
||||||
{
|
|
||||||
log_stream->Release();
|
|
||||||
FileSystem::DeleteFile(filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Align the first sector to 4K, so we better utilize the OS's page cache.
|
|
||||||
u64 pos = log_stream->GetPosition();
|
|
||||||
if (!Common::IsAlignedPow2(pos, sector_size))
|
|
||||||
{
|
|
||||||
u64 padding_end = Common::AlignUpPow2(pos, sector_size);
|
|
||||||
while (pos < padding_end)
|
|
||||||
{
|
|
||||||
u64 data = 0;
|
|
||||||
u64 size = std::min(padding_end - pos, u64(sizeof(data)));
|
|
||||||
if (!log_stream->Write2(&data, static_cast<u32>(size)))
|
|
||||||
{
|
|
||||||
log_stream->Release();
|
|
||||||
FileSystem::DeleteFile(filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
pos += size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!log_stream->Flush())
|
|
||||||
{
|
|
||||||
log_stream->Release();
|
|
||||||
FileSystem::DeleteFile(filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return log_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteStream* HDDImage::OpenLogFile(const char* filename, u64 image_size, u32& sector_size, u32& num_sectors,
|
|
||||||
u32& version_number, LogSectorMap& sector_map)
|
|
||||||
{
|
|
||||||
ByteStream* log_stream =
|
|
||||||
FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_SEEKABLE);
|
|
||||||
if (!log_stream)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
// Read in the image header.
|
|
||||||
LOG_FILE_HEADER header;
|
|
||||||
if (!log_stream->Read2(&header, sizeof(header)) || header.magic != LOG_FILE_MAGIC ||
|
|
||||||
header.image_size != image_size || header.sector_size == 0 || !Common::IsPow2(header.sector_size))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Log file '%s': Invalid header", filename);
|
|
||||||
log_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
num_sectors = static_cast<u32>(image_size / header.sector_size);
|
|
||||||
if (num_sectors == 0 || header.sector_count != static_cast<u32>(num_sectors))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Log file '%s': Corrupted header", filename);
|
|
||||||
log_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read in the sector map.
|
|
||||||
sector_size = header.sector_size;
|
|
||||||
version_number = header.version_number;
|
|
||||||
sector_map.resize(num_sectors);
|
|
||||||
if (!log_stream->Read2(sector_map.data(), static_cast<u32>(sizeof(SectorIndex) * num_sectors)))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to read sector map from '%s'", filename);
|
|
||||||
log_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return log_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
HDDImage::SectorBuffer& HDDImage::GetSector(SectorIndex sector_index)
|
|
||||||
{
|
|
||||||
if (m_current_sector.sector_number == sector_index)
|
|
||||||
return m_current_sector;
|
|
||||||
|
|
||||||
// Unload current sector and replace it.
|
|
||||||
ReleaseSector(m_current_sector);
|
|
||||||
LoadSector(m_current_sector, sector_index);
|
|
||||||
return m_current_sector;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::LoadSector(SectorBuffer& buf, SectorIndex sector_index)
|
|
||||||
{
|
|
||||||
Assert(sector_index != InvalidSectorNumber && sector_index < m_log_sector_map.size());
|
|
||||||
if (m_log_sector_map[sector_index] == InvalidSectorNumber)
|
|
||||||
LoadSectorFromImage(buf, sector_index);
|
|
||||||
else
|
|
||||||
LoadSectorFromLog(buf, sector_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<HDDImage> HDDImage::Create(const char* filename, u64 size_in_bytes,
|
|
||||||
u32 sector_size /*= DefaultReplaySectorSize*/)
|
|
||||||
{
|
|
||||||
String log_filename = GetLogFileName(filename);
|
|
||||||
if (FileSystem::FileExists(filename) || FileSystem::FileExists(log_filename))
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
ByteStream* base_stream = FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE |
|
|
||||||
BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_SEEKABLE);
|
|
||||||
if (!base_stream)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
// Write zeros to the image file.
|
|
||||||
u64 image_size = 0;
|
|
||||||
while (image_size < size_in_bytes)
|
|
||||||
{
|
|
||||||
u64 data = 0;
|
|
||||||
const u32 to_write = static_cast<u32>(std::min(size_in_bytes - image_size, u64(sizeof(data))));
|
|
||||||
if (!base_stream->Write2(&data, to_write))
|
|
||||||
{
|
|
||||||
base_stream->Release();
|
|
||||||
FileSystem::DeleteFile(filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
image_size += to_write;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the log.
|
|
||||||
u32 sector_count;
|
|
||||||
LogSectorMap sector_map;
|
|
||||||
ByteStream* log_stream =
|
|
||||||
CreateLogFile(log_filename, false, false, image_size, sector_size, sector_count, 0, sector_map);
|
|
||||||
if (!log_stream)
|
|
||||||
{
|
|
||||||
base_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::unique_ptr<HDDImage>(
|
|
||||||
new HDDImage(filename, base_stream, log_stream, image_size, sector_size, sector_count, 0, std::move(sector_map)));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<HDDImage> HDDImage::Open(const char* filename, u32 sector_size /* = DefaultReplaySectorSize */)
|
|
||||||
{
|
|
||||||
ByteStream* base_stream =
|
|
||||||
FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_SEEKABLE);
|
|
||||||
if (!base_stream)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
u64 image_size = base_stream->GetSize();
|
|
||||||
if (image_size == 0)
|
|
||||||
{
|
|
||||||
base_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
String log_filename = GetLogFileName(filename);
|
|
||||||
u32 sector_count;
|
|
||||||
u32 version_number = 0;
|
|
||||||
LogSectorMap sector_map;
|
|
||||||
ByteStream* log_stream;
|
|
||||||
if (FileSystem::FileExists(log_filename))
|
|
||||||
{
|
|
||||||
log_stream = OpenLogFile(log_filename, image_size, sector_size, sector_count, version_number, sector_map);
|
|
||||||
if (!log_stream)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to read log file for image '%s'.", filename);
|
|
||||||
base_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Log file not found for image '%s', creating.", filename);
|
|
||||||
log_stream =
|
|
||||||
CreateLogFile(log_filename, false, false, image_size, sector_size, sector_count, version_number, sector_map);
|
|
||||||
if (!log_stream)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to create log file for image '%s'.", filename);
|
|
||||||
base_stream->Release();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log_DevPrintf("Opened image '%s' with log file '%s' (sector size %u)", filename, log_filename.GetCharArray(),
|
|
||||||
sector_size);
|
|
||||||
return std::unique_ptr<HDDImage>(new HDDImage(filename, base_stream, log_stream, image_size, sector_size,
|
|
||||||
sector_count, version_number, std::move(sector_map)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::LoadSectorFromImage(SectorBuffer& buf, SectorIndex sector_index)
|
|
||||||
{
|
|
||||||
if (!m_base_stream->SeekAbsolute(GetFileOffset(sector_index)) || !m_base_stream->Read2(buf.data.get(), m_sector_size))
|
|
||||||
Panic("Failed to read from base image.");
|
|
||||||
|
|
||||||
buf.sector_number = sector_index;
|
|
||||||
buf.dirty = false;
|
|
||||||
buf.in_log = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::LoadSectorFromLog(SectorBuffer& buf, SectorIndex sector_index)
|
|
||||||
{
|
|
||||||
DebugAssert(sector_index < m_sector_count);
|
|
||||||
|
|
||||||
const SectorIndex log_sector_index = m_log_sector_map[sector_index];
|
|
||||||
Assert(log_sector_index != InvalidSectorNumber);
|
|
||||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(log_sector_index)) ||
|
|
||||||
!m_log_stream->Read2(buf.data.get(), m_sector_size))
|
|
||||||
{
|
|
||||||
Panic("Failed to read from log file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.sector_number = sector_index;
|
|
||||||
buf.dirty = false;
|
|
||||||
buf.in_log = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::WriteSectorToLog(SectorBuffer& buf)
|
|
||||||
{
|
|
||||||
DebugAssert(buf.dirty && buf.sector_number < m_sector_count);
|
|
||||||
|
|
||||||
// Is the sector currently in the log?
|
|
||||||
if (!buf.in_log)
|
|
||||||
{
|
|
||||||
Assert(m_log_sector_map[buf.sector_number] == InvalidSectorNumber);
|
|
||||||
|
|
||||||
// Need to allocate it in the log file.
|
|
||||||
if (!m_log_stream->SeekToEnd())
|
|
||||||
Panic("Failed to seek to end of log.");
|
|
||||||
|
|
||||||
const u64 sector_offset = m_log_stream->GetPosition();
|
|
||||||
const SectorIndex log_sector_number = static_cast<SectorIndex>(sector_offset / m_sector_size);
|
|
||||||
Log_DevPrintf("Allocating log sector %u to sector %u", buf.sector_number, log_sector_number);
|
|
||||||
m_log_sector_map[buf.sector_number] = log_sector_number;
|
|
||||||
|
|
||||||
// Update log sector map in file.
|
|
||||||
if (!m_log_stream->SeekAbsolute(GetSectorMapOffset(buf.sector_number)) ||
|
|
||||||
!m_log_stream->Write2(&log_sector_number, sizeof(SectorIndex)))
|
|
||||||
{
|
|
||||||
Panic("Failed to update sector map in log file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.in_log = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to the log.
|
|
||||||
const SectorIndex log_sector_index = m_log_sector_map[buf.sector_number];
|
|
||||||
Assert(log_sector_index != InvalidSectorNumber);
|
|
||||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(log_sector_index)) ||
|
|
||||||
!m_log_stream->Write2(buf.data.get(), m_sector_size))
|
|
||||||
{
|
|
||||||
Panic("Failed to write sector to log file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::ReleaseSector(SectorBuffer& buf)
|
|
||||||
{
|
|
||||||
// Write it to the log file if it's changed.
|
|
||||||
if (m_current_sector.dirty)
|
|
||||||
WriteSectorToLog(m_current_sector);
|
|
||||||
|
|
||||||
m_current_sector.sector_number = InvalidSectorNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::ReleaseAllSectors()
|
|
||||||
{
|
|
||||||
if (m_current_sector.sector_number == InvalidSectorNumber)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ReleaseSector(m_current_sector);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::Read(void* buffer, u64 offset, u32 size)
|
|
||||||
{
|
|
||||||
Assert((offset + size) <= m_image_size);
|
|
||||||
|
|
||||||
byte* buf = reinterpret_cast<byte*>(buffer);
|
|
||||||
while (size > 0)
|
|
||||||
{
|
|
||||||
// Find the sector that this offset lives in.
|
|
||||||
const SectorIndex sector_index = static_cast<SectorIndex>(offset / m_sector_size);
|
|
||||||
const u32 offset_in_sector = static_cast<u32>(offset % m_sector_size);
|
|
||||||
const u32 size_to_read = std::min(size, m_sector_size - offset_in_sector);
|
|
||||||
|
|
||||||
// Load the sector, and read the sub-sector.
|
|
||||||
const SectorBuffer& sec = GetSector(sector_index);
|
|
||||||
std::memcpy(buf, &sec.data[offset_in_sector], size_to_read);
|
|
||||||
buf += size_to_read;
|
|
||||||
offset += size_to_read;
|
|
||||||
size -= size_to_read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::Write(const void* buffer, u64 offset, u32 size)
|
|
||||||
{
|
|
||||||
Assert((offset + size) <= m_image_size);
|
|
||||||
|
|
||||||
const byte* buf = reinterpret_cast<const byte*>(buffer);
|
|
||||||
while (size > 0)
|
|
||||||
{
|
|
||||||
// Find the sector that this offset lives in.
|
|
||||||
const SectorIndex sector_index = static_cast<SectorIndex>(offset / m_sector_size);
|
|
||||||
const u32 offset_in_sector = static_cast<u32>(offset % m_sector_size);
|
|
||||||
const u32 size_to_write = std::min(size, m_sector_size - offset_in_sector);
|
|
||||||
|
|
||||||
// Load the sector, and update it.
|
|
||||||
SectorBuffer& sec = GetSector(sector_index);
|
|
||||||
std::memcpy(&sec.data[offset_in_sector], buf, size_to_write);
|
|
||||||
sec.dirty = true;
|
|
||||||
buf += size_to_write;
|
|
||||||
offset += size_to_write;
|
|
||||||
size -= size_to_write;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HDDImage::LoadState(ByteStream* stream)
|
|
||||||
{
|
|
||||||
ReleaseAllSectors();
|
|
||||||
|
|
||||||
// Read header in from stream. It may not be valid.
|
|
||||||
STATE_HEADER header;
|
|
||||||
if (!stream->Read2(&header, sizeof(header)) || header.magic != STATE_MAGIC || header.image_size != m_image_size ||
|
|
||||||
header.sector_size != m_sector_size || header.sector_count != m_sector_count)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Corrupted save state.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The version number could have changed, which means we committed since this state was saved.
|
|
||||||
if (header.version_number != m_version_number)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Incorrect version number in save state (%u, should be %u), it is a stale state",
|
|
||||||
header.version_number, m_version_number);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Okay, everything seems fine. We can now throw away the current log file, and re-write it.
|
|
||||||
LogSectorMap new_sector_map;
|
|
||||||
ByteStream* new_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, true, m_image_size,
|
|
||||||
m_sector_size, m_sector_count, m_version_number, new_sector_map);
|
|
||||||
if (!new_log_stream)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Write sectors from log.
|
|
||||||
for (u32 i = 0; i < header.num_sectors_in_state; i++)
|
|
||||||
{
|
|
||||||
const SectorIndex log_sector_index = static_cast<SectorIndex>(new_log_stream->GetPosition() / m_sector_size);
|
|
||||||
SectorIndex sector_index;
|
|
||||||
if (!stream->Read2(§or_index, sizeof(sector_index)) || sector_index >= m_sector_count ||
|
|
||||||
!ByteStream_CopyBytes(stream, m_sector_size, new_log_stream))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to copy new sector from save state.");
|
|
||||||
new_log_stream->Discard();
|
|
||||||
new_log_stream->Release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update new sector map.
|
|
||||||
new_sector_map[sector_index] = log_sector_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the new sector map.
|
|
||||||
if (!new_log_stream->SeekAbsolute(GetSectorMapOffset(0)) ||
|
|
||||||
!new_log_stream->Write2(new_sector_map.data(), sizeof(SectorIndex) * m_sector_count))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to write new sector map from save state.");
|
|
||||||
new_log_stream->Discard();
|
|
||||||
new_log_stream->Release();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the stream, replacing the existing file. Then swap the pointers, since we may as well use the existing one.
|
|
||||||
m_log_stream->Release();
|
|
||||||
new_log_stream->Flush();
|
|
||||||
new_log_stream->Commit();
|
|
||||||
m_log_stream = new_log_stream;
|
|
||||||
m_log_sector_map = std::move(new_sector_map);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HDDImage::SaveState(ByteStream* stream)
|
|
||||||
{
|
|
||||||
ReleaseAllSectors();
|
|
||||||
|
|
||||||
// Precompute how many sectors are committed to the log.
|
|
||||||
u32 log_sector_count = 0;
|
|
||||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
|
||||||
{
|
|
||||||
if (IsSectorInLog(sector_index))
|
|
||||||
log_sector_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct header.
|
|
||||||
STATE_HEADER header = {};
|
|
||||||
header.magic = STATE_MAGIC;
|
|
||||||
header.sector_size = m_sector_size;
|
|
||||||
header.image_size = m_image_size;
|
|
||||||
header.sector_count = m_sector_count;
|
|
||||||
header.version_number = m_version_number;
|
|
||||||
header.num_sectors_in_state = log_sector_count;
|
|
||||||
if (!stream->Write2(&header, sizeof(header)))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to write log header to save state.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy each sector from the replay log.
|
|
||||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
|
||||||
{
|
|
||||||
if (!IsSectorInLog(sector_index))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(m_log_sector_map[sector_index])) ||
|
|
||||||
!stream->Write2(§or_index, sizeof(sector_index)) ||
|
|
||||||
!ByteStream_CopyBytes(m_log_stream, m_sector_size, stream))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to write log sector to save state.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::Flush()
|
|
||||||
{
|
|
||||||
if (!m_current_sector.dirty)
|
|
||||||
return;
|
|
||||||
|
|
||||||
WriteSectorToLog(m_current_sector);
|
|
||||||
|
|
||||||
// Ensure the stream isn't buffering.
|
|
||||||
if (!m_log_stream->Flush())
|
|
||||||
Panic("Failed to flush log stream.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::CommitLog()
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Committing log for '%s'.", m_filename.c_str());
|
|
||||||
ReleaseAllSectors();
|
|
||||||
|
|
||||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
|
||||||
{
|
|
||||||
if (!IsSectorInLog(sector_index))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Read log sector to buffer, then write it to the base image.
|
|
||||||
// No need to update the log map, since we trash it anyway.
|
|
||||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(m_log_sector_map[sector_index])) ||
|
|
||||||
!m_base_stream->SeekAbsolute(GetFileOffset(sector_index)) ||
|
|
||||||
ByteStream_CopyBytes(m_log_stream, m_sector_size, m_base_stream) != m_sector_size)
|
|
||||||
{
|
|
||||||
Panic("Failed to transfer sector from log to base image.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increment the version number, to invalidate old save states.
|
|
||||||
m_version_number++;
|
|
||||||
|
|
||||||
// Truncate the log, and re-create it.
|
|
||||||
m_log_stream->Release();
|
|
||||||
m_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, false, m_image_size, m_sector_size,
|
|
||||||
m_sector_count, m_version_number, m_log_sector_map);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HDDImage::RevertLog()
|
|
||||||
{
|
|
||||||
Log_InfoPrintf("Reverting log for '%s'", m_filename.c_str());
|
|
||||||
ReleaseAllSectors();
|
|
||||||
|
|
||||||
m_log_stream->Release();
|
|
||||||
m_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, false, m_image_size, m_sector_size,
|
|
||||||
m_sector_count, m_version_number, m_log_sector_map);
|
|
||||||
if (!m_log_stream)
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to recreate log file for image '%s'", m_filename.c_str());
|
|
||||||
Panic("Failed to recreate log file.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "YBaseLib/ByteStream.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class HDDImage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using SectorIndex = u32;
|
|
||||||
|
|
||||||
static constexpr u32 InvalidSectorNumber = UINT32_C(0xFFFFFFFF);
|
|
||||||
static constexpr u32 DefaultSectorSize = 4096;
|
|
||||||
|
|
||||||
static std::unique_ptr<HDDImage> Create(const char* filename, u64 size_in_bytes, u32 sector_size = DefaultSectorSize);
|
|
||||||
static std::unique_ptr<HDDImage> Open(const char* filename, u32 sector_size = DefaultSectorSize);
|
|
||||||
|
|
||||||
~HDDImage();
|
|
||||||
|
|
||||||
const u64 GetImageSize() const { return m_image_size; }
|
|
||||||
const u32 GetSectorSize() const { return m_sector_size; }
|
|
||||||
const u32 GetSectorCount() const { return m_sector_count; }
|
|
||||||
|
|
||||||
void Read(void* buffer, u64 offset, u32 size);
|
|
||||||
void Write(const void* buffer, u64 offset, u32 size);
|
|
||||||
|
|
||||||
/// Erases the current replay log, and replaces it with the log from the specified stream.
|
|
||||||
bool LoadState(ByteStream* stream);
|
|
||||||
|
|
||||||
/// Copies the current state of the replay log to the specified stream, so it can be restored later.
|
|
||||||
bool SaveState(ByteStream* stream);
|
|
||||||
|
|
||||||
/// Flushes any buffered sectors to the backing file/log.
|
|
||||||
void Flush();
|
|
||||||
|
|
||||||
/// Commits all changes made in the replay log to the base image.
|
|
||||||
void CommitLog();
|
|
||||||
|
|
||||||
/// Erases any changes made in the replay log, restoring the image to its base state.
|
|
||||||
void RevertLog();
|
|
||||||
|
|
||||||
private:
|
|
||||||
using LogSectorMap = std::vector<SectorIndex>;
|
|
||||||
struct SectorBuffer
|
|
||||||
{
|
|
||||||
std::unique_ptr<byte[]> data;
|
|
||||||
SectorIndex sector_number = InvalidSectorNumber;
|
|
||||||
bool in_log = false;
|
|
||||||
bool dirty = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
HDDImage(const std::string filename, ByteStream* base_stream, ByteStream* log_stream, u64 size, u32 sector_size,
|
|
||||||
u32 sector_count, u32 version_number, LogSectorMap log_sector_map);
|
|
||||||
|
|
||||||
static ByteStream* CreateLogFile(const char* filename, bool truncate_existing, bool atomic_update, u64 image_size,
|
|
||||||
u32 sector_size, u32& num_sectors, u32 version_number, LogSectorMap& sector_map);
|
|
||||||
static ByteStream* OpenLogFile(const char* filename, u64 image_size, u32& sector_size, u32& num_sectors,
|
|
||||||
u32& version_number, LogSectorMap& sector_map);
|
|
||||||
|
|
||||||
// Returns the offset in the image (either log or base) for the specified sector.
|
|
||||||
u64 GetFileOffset(SectorIndex sector_index) const
|
|
||||||
{
|
|
||||||
return static_cast<u64>(sector_index) * static_cast<u64>(m_sector_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns whether the specified sector is in the log (true), or in the base image (false).
|
|
||||||
bool IsSectorInLog(SectorIndex sector_index) const { return (m_log_sector_map[sector_index] != InvalidSectorNumber); }
|
|
||||||
|
|
||||||
// Currently, we only have one sector open. But we could change this in the future.
|
|
||||||
SectorBuffer& GetSector(SectorIndex sector_index);
|
|
||||||
|
|
||||||
void LoadSector(SectorBuffer& buf, SectorIndex sector_index);
|
|
||||||
void LoadSectorFromImage(SectorBuffer& buf, SectorIndex sector_index);
|
|
||||||
void LoadSectorFromLog(SectorBuffer& buf, SectorIndex sector_index);
|
|
||||||
void WriteSectorToLog(SectorBuffer& buf);
|
|
||||||
void ReleaseSector(SectorBuffer& buf);
|
|
||||||
void ReleaseAllSectors();
|
|
||||||
|
|
||||||
std::string m_filename;
|
|
||||||
|
|
||||||
ByteStream* m_base_stream;
|
|
||||||
ByteStream* m_log_stream;
|
|
||||||
|
|
||||||
u64 m_image_size;
|
|
||||||
u32 m_sector_size;
|
|
||||||
u32 m_sector_count;
|
|
||||||
u32 m_version_number;
|
|
||||||
|
|
||||||
LogSectorMap m_log_sector_map;
|
|
||||||
|
|
||||||
SectorBuffer m_current_sector;
|
|
||||||
};
|
|
|
@ -1,8 +0,0 @@
|
||||||
#include "common/object.h"
|
|
||||||
|
|
||||||
// Have to define this manually as Object has no parent class.
|
|
||||||
ObjectTypeInfo Object::s_type_info("Object", nullptr, nullptr, nullptr);
|
|
||||||
|
|
||||||
Object::Object(const ObjectTypeInfo* pObjectTypeInfo /* = &s_typeInfo */) : m_type_info(pObjectTypeInfo) {}
|
|
||||||
|
|
||||||
Object::~Object() = default;
|
|
|
@ -1,84 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "object_type_info.h"
|
|
||||||
|
|
||||||
class Object
|
|
||||||
{
|
|
||||||
// OBJECT TYPE STUFF
|
|
||||||
private:
|
|
||||||
static ObjectTypeInfo s_type_info;
|
|
||||||
|
|
||||||
public:
|
|
||||||
typedef Object ThisClass;
|
|
||||||
static const ObjectTypeInfo* StaticTypeInfo() { return &s_type_info; }
|
|
||||||
static ObjectTypeInfo* StaticMutableTypeInfo() { return &s_type_info; }
|
|
||||||
static const PROPERTY_DECLARATION* StaticPropertyMap() { return nullptr; }
|
|
||||||
static ObjectFactory* StaticFactory() { return nullptr; }
|
|
||||||
// END OBJECT TYPE STUFF
|
|
||||||
|
|
||||||
public:
|
|
||||||
Object(const ObjectTypeInfo* type_info = &s_type_info);
|
|
||||||
virtual ~Object();
|
|
||||||
|
|
||||||
// Retrieves the type information for this object.
|
|
||||||
const ObjectTypeInfo* GetTypeInfo() const { return m_type_info; }
|
|
||||||
|
|
||||||
// Cast from one object type to another, unchecked.
|
|
||||||
template<class T>
|
|
||||||
const T* Cast() const
|
|
||||||
{
|
|
||||||
DebugAssert(m_type_info->IsDerived(T::StaticTypeInfo()));
|
|
||||||
return static_cast<const T*>(this);
|
|
||||||
}
|
|
||||||
template<class T>
|
|
||||||
T* Cast()
|
|
||||||
{
|
|
||||||
DebugAssert(m_type_info->IsDerived(T::StaticTypeInfo()));
|
|
||||||
return static_cast<T*>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cast from one object type to another, checked.
|
|
||||||
template<class T>
|
|
||||||
const T* SafeCast() const
|
|
||||||
{
|
|
||||||
return (m_type_info->IsDerived(T::StaticTypeInfo())) ? static_cast<const T*>(this) : nullptr;
|
|
||||||
}
|
|
||||||
template<class T>
|
|
||||||
T* SafeCast()
|
|
||||||
{
|
|
||||||
return (m_type_info->IsDerived(T::StaticTypeInfo())) ? static_cast<T*>(this) : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test if one object type is derived from another.
|
|
||||||
template<class T>
|
|
||||||
bool IsDerived() const
|
|
||||||
{
|
|
||||||
return (m_type_info->IsDerived(T::StaticTypeInfo()));
|
|
||||||
}
|
|
||||||
bool IsDerived(const ObjectTypeInfo* type) const { return (m_type_info->IsDerived(type)); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Type info pointer. Set by subclasses.
|
|
||||||
const ObjectTypeInfo* m_type_info;
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// GenericObjectFactory<T>
|
|
||||||
//
|
|
||||||
template<class T>
|
|
||||||
struct GenericObjectFactory final : public ObjectFactory
|
|
||||||
{
|
|
||||||
Object* CreateObject() override { return new T(); }
|
|
||||||
Object* CreateObject(const String& identifier) override { return new T(); }
|
|
||||||
void DeleteObject(Object* object) override { delete object; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#define DECLARE_OBJECT_GENERIC_FACTORY(Type) \
|
|
||||||
\
|
|
||||||
private: \
|
|
||||||
static GenericObjectFactory<Type> s_GenericFactory; \
|
|
||||||
\
|
|
||||||
public: \
|
|
||||||
static ObjectFactory* StaticFactory() { return &s_GenericFactory; }
|
|
||||||
|
|
||||||
#define DEFINE_OBJECT_GENERIC_FACTORY(Type) \
|
|
||||||
GenericObjectFactory<Type> Type::s_GenericFactory = GenericObjectFactory<Type>();
|
|
|
@ -1,129 +0,0 @@
|
||||||
#include "common/object_type_info.h"
|
|
||||||
|
|
||||||
static ObjectTypeInfo::RegistryType s_registry;
|
|
||||||
|
|
||||||
ObjectTypeInfo::RegistryType& ObjectTypeInfo::GetRegistry()
|
|
||||||
{
|
|
||||||
return s_registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectTypeInfo::ObjectTypeInfo(const char* TypeName, const ObjectTypeInfo* pParentTypeInfo,
|
|
||||||
const PROPERTY_DECLARATION* pPropertyDeclarations, ObjectFactory* pFactory)
|
|
||||||
: m_type_index(INVALID_OBJECT_TYPE_INDEX), m_inheritance_depth(0), m_type_name(TypeName),
|
|
||||||
m_parent_type(pParentTypeInfo), m_factory(pFactory), m_source_property_declarations(pPropertyDeclarations),
|
|
||||||
m_property_declarations(nullptr), m_num_property_declarations(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectTypeInfo::~ObjectTypeInfo()
|
|
||||||
{
|
|
||||||
// DebugAssert(m_iTypeIndex == INVALID_TYPE_INDEX);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ObjectTypeInfo::CanCreateInstance() const
|
|
||||||
{
|
|
||||||
return (m_factory != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object* ObjectTypeInfo::CreateInstance() const
|
|
||||||
{
|
|
||||||
DebugAssert(m_factory != nullptr);
|
|
||||||
return m_factory->CreateObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectTypeInfo::DestroyInstance(Object* obj) const
|
|
||||||
{
|
|
||||||
DebugAssert(m_factory != nullptr);
|
|
||||||
m_factory->DeleteObject(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ObjectTypeInfo::IsDerived(const ObjectTypeInfo* pTypeInfo) const
|
|
||||||
{
|
|
||||||
const ObjectTypeInfo* current_type = this;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (current_type == pTypeInfo)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
current_type = current_type->m_parent_type;
|
|
||||||
} while (current_type != nullptr);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROPERTY_DECLARATION* ObjectTypeInfo::GetPropertyDeclarationByName(const char* PropertyName) const
|
|
||||||
{
|
|
||||||
for (u32 i = 0; i < m_num_property_declarations; i++)
|
|
||||||
{
|
|
||||||
if (!Y_stricmp(m_property_declarations[i]->Name, PropertyName))
|
|
||||||
return m_property_declarations[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectTypeInfo::RegisterType()
|
|
||||||
{
|
|
||||||
if (m_type_index != INVALID_OBJECT_TYPE_INDEX)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// our stuff
|
|
||||||
const ObjectTypeInfo* pCurrentTypeInfo;
|
|
||||||
const PROPERTY_DECLARATION* pPropertyDeclaration;
|
|
||||||
|
|
||||||
// get property count
|
|
||||||
pCurrentTypeInfo = this;
|
|
||||||
m_num_property_declarations = 0;
|
|
||||||
m_inheritance_depth = 0;
|
|
||||||
while (pCurrentTypeInfo != nullptr)
|
|
||||||
{
|
|
||||||
if (pCurrentTypeInfo->m_source_property_declarations != nullptr)
|
|
||||||
{
|
|
||||||
pPropertyDeclaration = pCurrentTypeInfo->m_source_property_declarations;
|
|
||||||
while (pPropertyDeclaration->Name != nullptr)
|
|
||||||
{
|
|
||||||
m_num_property_declarations++;
|
|
||||||
pPropertyDeclaration++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pCurrentTypeInfo = pCurrentTypeInfo->GetParentType();
|
|
||||||
m_inheritance_depth++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_num_property_declarations > 0)
|
|
||||||
{
|
|
||||||
m_property_declarations = new const PROPERTY_DECLARATION*[m_num_property_declarations];
|
|
||||||
pCurrentTypeInfo = this;
|
|
||||||
u32 i = 0;
|
|
||||||
while (pCurrentTypeInfo != nullptr)
|
|
||||||
{
|
|
||||||
if (pCurrentTypeInfo->m_source_property_declarations != nullptr)
|
|
||||||
{
|
|
||||||
pPropertyDeclaration = pCurrentTypeInfo->m_source_property_declarations;
|
|
||||||
while (pPropertyDeclaration->Name != nullptr)
|
|
||||||
{
|
|
||||||
DebugAssert(i < m_num_property_declarations);
|
|
||||||
m_property_declarations[i++] = pPropertyDeclaration++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pCurrentTypeInfo = pCurrentTypeInfo->GetParentType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_type_index = GetRegistry().RegisterTypeInfo(this, m_type_name, m_inheritance_depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectTypeInfo::UnregisterType()
|
|
||||||
{
|
|
||||||
if (m_type_index == INVALID_OBJECT_TYPE_INDEX)
|
|
||||||
return;
|
|
||||||
|
|
||||||
delete[] m_property_declarations;
|
|
||||||
m_property_declarations = nullptr;
|
|
||||||
m_num_property_declarations = 0;
|
|
||||||
|
|
||||||
m_type_index = INVALID_OBJECT_TYPE_INDEX;
|
|
||||||
GetRegistry().UnregisterTypeInfo(this);
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/property.h"
|
|
||||||
#include "common/type_registry.h"
|
|
||||||
#include "common/types.h"
|
|
||||||
|
|
||||||
// Forward declare the factory type.
|
|
||||||
class Object;
|
|
||||||
struct ObjectFactory;
|
|
||||||
|
|
||||||
//
|
|
||||||
// ObjectTypeInfo
|
|
||||||
//
|
|
||||||
class ObjectTypeInfo
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Constants.
|
|
||||||
static constexpr u32 INVALID_OBJECT_TYPE_INDEX = 0xFFFFFFFF;
|
|
||||||
using RegistryType = TypeRegistry<ObjectTypeInfo>;
|
|
||||||
|
|
||||||
// constructors
|
|
||||||
ObjectTypeInfo(const char* TypeName, const ObjectTypeInfo* pParentTypeInfo,
|
|
||||||
const PROPERTY_DECLARATION* pPropertyDeclarations, ObjectFactory* pFactory);
|
|
||||||
virtual ~ObjectTypeInfo();
|
|
||||||
|
|
||||||
// accessors
|
|
||||||
const u32 GetTypeIndex() const { return m_type_index; }
|
|
||||||
const u32 GetInheritanceDepth() const { return m_inheritance_depth; }
|
|
||||||
const char* GetTypeName() const { return m_type_name; }
|
|
||||||
const ObjectTypeInfo* GetParentType() const { return m_parent_type; }
|
|
||||||
ObjectFactory* GetFactory() const { return m_factory; }
|
|
||||||
|
|
||||||
// can create?
|
|
||||||
bool CanCreateInstance() const;
|
|
||||||
Object* CreateInstance() const;
|
|
||||||
void DestroyInstance(Object* obj) const;
|
|
||||||
|
|
||||||
// type information
|
|
||||||
// currently only does single inheritance
|
|
||||||
bool IsDerived(const ObjectTypeInfo* type) const;
|
|
||||||
|
|
||||||
// properties
|
|
||||||
const PROPERTY_DECLARATION* GetPropertyDeclarationByName(const char* name) const;
|
|
||||||
const PROPERTY_DECLARATION* GetPropertyDeclarationByIndex(u32 index) const
|
|
||||||
{
|
|
||||||
DebugAssert(index < m_num_property_declarations);
|
|
||||||
return m_property_declarations[index];
|
|
||||||
}
|
|
||||||
u32 GetPropertyCount() const { return m_num_property_declarations; }
|
|
||||||
|
|
||||||
// only called once.
|
|
||||||
virtual void RegisterType();
|
|
||||||
virtual void UnregisterType();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
u32 m_type_index;
|
|
||||||
u32 m_inheritance_depth;
|
|
||||||
const char* m_type_name;
|
|
||||||
const ObjectTypeInfo* m_parent_type;
|
|
||||||
ObjectFactory* m_factory;
|
|
||||||
|
|
||||||
// properties
|
|
||||||
const PROPERTY_DECLARATION* m_source_property_declarations;
|
|
||||||
const PROPERTY_DECLARATION** m_property_declarations;
|
|
||||||
u32 m_num_property_declarations;
|
|
||||||
|
|
||||||
// TYPE REGISTRY
|
|
||||||
public:
|
|
||||||
static RegistryType& GetRegistry();
|
|
||||||
// END TYPE REGISTRY
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// ObjectFactory
|
|
||||||
//
|
|
||||||
struct ObjectFactory
|
|
||||||
{
|
|
||||||
virtual Object* CreateObject() = 0;
|
|
||||||
virtual Object* CreateObject(const String& identifier) = 0;
|
|
||||||
virtual void DeleteObject(Object* object) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Macros
|
|
||||||
#define DECLARE_OBJECT_TYPE_INFO(Type, ParentType) \
|
|
||||||
\
|
|
||||||
private: \
|
|
||||||
static ObjectTypeInfo s_type_info; \
|
|
||||||
\
|
|
||||||
public: \
|
|
||||||
typedef Type ThisClass; \
|
|
||||||
typedef ParentType BaseClass; \
|
|
||||||
static const ObjectTypeInfo* StaticTypeInfo() { return &s_type_info; } \
|
|
||||||
static ObjectTypeInfo* StaticMutableTypeInfo() { return &s_type_info; }
|
|
||||||
|
|
||||||
#define DECLARE_OBJECT_PROPERTY_MAP(Type) \
|
|
||||||
\
|
|
||||||
private: \
|
|
||||||
static const PROPERTY_DECLARATION s_propertyDeclarations[]; \
|
|
||||||
static const PROPERTY_DECLARATION* StaticPropertyMap() { return s_propertyDeclarations; }
|
|
||||||
|
|
||||||
#define DECLARE_OBJECT_NO_PROPERTIES(Type) \
|
|
||||||
\
|
|
||||||
private: \
|
|
||||||
static const PROPERTY_DECLARATION* StaticPropertyMap() { return nullptr; }
|
|
||||||
|
|
||||||
#define DEFINE_OBJECT_TYPE_INFO(Type) \
|
|
||||||
ObjectTypeInfo Type::s_type_info(#Type, Type::BaseClass::StaticTypeInfo(), Type::StaticPropertyMap(), \
|
|
||||||
Type::StaticFactory())
|
|
||||||
|
|
||||||
#define DEFINE_NAMED_OBJECT_TYPE_INFO(Type, Name) \
|
|
||||||
ObjectTypeInfo Type::s_type_info(Name, Type::BaseClass::StaticTypeInfo(), Type::StaticPropertyMap(), \
|
|
||||||
Type::StaticFactory())
|
|
||||||
|
|
||||||
#define DECLARE_OBJECT_NO_FACTORY(Type) \
|
|
||||||
\
|
|
||||||
public: \
|
|
||||||
static ObjectFactory* StaticFactory() { return nullptr; }
|
|
||||||
|
|
||||||
#define BEGIN_OBJECT_PROPERTY_MAP(Type) const PROPERTY_DECLARATION Type::s_propertyDeclarations[] = {
|
|
||||||
|
|
||||||
#define END_OBJECT_PROPERTY_MAP() \
|
|
||||||
PROPERTY_TABLE_MEMBER(NULL, PROPERTY_TYPE_COUNT, 0, NULL, NULL, NULL, NULL, NULL, NULL) \
|
|
||||||
} \
|
|
||||||
;
|
|
||||||
|
|
||||||
#define OBJECT_TYPEINFO(Type) Type::StaticTypeInfo()
|
|
||||||
#define OBJECT_TYPEINFO_PTR(Ptr) Ptr->StaticTypeInfo()
|
|
||||||
|
|
||||||
#define OBJECT_MUTABLE_TYPEINFO(Type) Type::StaticMutableTypeInfo()
|
|
||||||
#define OBJECT_MUTABLE_TYPEINFO_PTR(Type) Type->StaticMutableTypeInfo()
|
|
|
@ -1,349 +0,0 @@
|
||||||
#include "common/property.h"
|
|
||||||
#include "YBaseLib/BinaryReader.h"
|
|
||||||
#include "YBaseLib/BinaryWriter.h"
|
|
||||||
#include "YBaseLib/StringConverter.h"
|
|
||||||
|
|
||||||
bool GetPropertyValueAsString(const void* object, const PROPERTY_DECLARATION* property, String& value)
|
|
||||||
{
|
|
||||||
if (!property->GetPropertyCallback)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Strings handled seperately.
|
|
||||||
if (property->Type == PROPERTY_TYPE_STRING)
|
|
||||||
{
|
|
||||||
// We can pass StrValue directly across.
|
|
||||||
return property->GetPropertyCallback(object, property->pGetPropertyCallbackUserData, &value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 32 bytes should be enough for the actual value. (largest is currently transform, which is float3 + quat + float)
|
|
||||||
byte TempValue[32];
|
|
||||||
|
|
||||||
// Call the function.
|
|
||||||
if (!property->GetPropertyCallback(object, property->pGetPropertyCallbackUserData, &TempValue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Now stringize it based on type.
|
|
||||||
switch (property->Type)
|
|
||||||
{
|
|
||||||
case PROPERTY_TYPE_BOOL:
|
|
||||||
StringConverter::BoolToString(value, reinterpret_cast<const bool&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_UINT:
|
|
||||||
StringConverter::UInt32ToString(value, reinterpret_cast<const u32&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_INT:
|
|
||||||
StringConverter::Int32ToString(value, reinterpret_cast<const s32&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_FLOAT:
|
|
||||||
StringConverter::FloatToString(value, reinterpret_cast<const float&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetPropertyValueFromString(void* object, const PROPERTY_DECLARATION* property, const char* value)
|
|
||||||
{
|
|
||||||
if (property->SetPropertyCallback == NULL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Strings handled seperately.
|
|
||||||
if (property->Type == PROPERTY_TYPE_STRING)
|
|
||||||
{
|
|
||||||
// Create a constant string.
|
|
||||||
StaticString StringRef(value);
|
|
||||||
if (!property->SetPropertyCallback(object, property->pSetPropertyCallbackUserData, &StringRef))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 32 bytes should be enough for the actual value. (largest is currently transform, which is float3 + quat + float)
|
|
||||||
byte TempValue[32];
|
|
||||||
|
|
||||||
// Un-stringize based on type.
|
|
||||||
switch (property->Type)
|
|
||||||
{
|
|
||||||
case PROPERTY_TYPE_BOOL:
|
|
||||||
reinterpret_cast<bool&>(TempValue) = StringConverter::StringToBool(value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_UINT:
|
|
||||||
reinterpret_cast<u32&>(TempValue) = StringConverter::StringToUInt32(value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_INT:
|
|
||||||
reinterpret_cast<s32&>(TempValue) = StringConverter::StringToInt32(value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_FLOAT:
|
|
||||||
reinterpret_cast<float&>(TempValue) = StringConverter::StringToFloat(value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the function.
|
|
||||||
if (!property->SetPropertyCallback(object, property->pSetPropertyCallbackUserData, TempValue))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify updater if needed.
|
|
||||||
// if (pProperty->PropertyChangedCallback != NULL)
|
|
||||||
// pProperty->PropertyChangedCallback(pObject, pProperty->pPropertyChangedCallbackUserData);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WritePropertyValueToBuffer(const void* object, const PROPERTY_DECLARATION* property, BinaryWriter& writer)
|
|
||||||
{
|
|
||||||
if (!property->GetPropertyCallback)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Strings handled seperately.
|
|
||||||
if (property->Type == PROPERTY_TYPE_STRING)
|
|
||||||
{
|
|
||||||
// We can pass StrValue directly across.
|
|
||||||
SmallString stringValue;
|
|
||||||
if (!property->GetPropertyCallback(object, property->pGetPropertyCallbackUserData, &stringValue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
writer.WriteUInt32(stringValue.GetLength() + 1);
|
|
||||||
writer.WriteCString(stringValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 32 bytes should be enough for the actual value. (largest is currently transform, which is float3 + quat + float)
|
|
||||||
byte TempValue[32];
|
|
||||||
|
|
||||||
// Call the function.
|
|
||||||
if (!property->GetPropertyCallback(object, property->pGetPropertyCallbackUserData, &TempValue))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Now stringize it based on type.
|
|
||||||
switch (property->Type)
|
|
||||||
{
|
|
||||||
case PROPERTY_TYPE_BOOL:
|
|
||||||
writer.WriteUInt32(1);
|
|
||||||
writer.WriteBool(reinterpret_cast<const bool&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_UINT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteUInt32(reinterpret_cast<const u32&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_INT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteInt32(reinterpret_cast<const s32&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_FLOAT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteFloat(reinterpret_cast<const float&>(TempValue));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ReadPropertyValueFromBuffer(void* object, const PROPERTY_DECLARATION* property, BinaryReader& reader)
|
|
||||||
{
|
|
||||||
if (!property->SetPropertyCallback)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Strings handled seperately.
|
|
||||||
if (property->Type == PROPERTY_TYPE_STRING)
|
|
||||||
{
|
|
||||||
u32 stringLength = reader.ReadUInt32();
|
|
||||||
|
|
||||||
SmallString stringValue;
|
|
||||||
reader.ReadCString(stringValue);
|
|
||||||
if (stringValue.GetLength() != (stringLength - 1) ||
|
|
||||||
!property->SetPropertyCallback(object, property->pSetPropertyCallbackUserData, &stringValue))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 32 bytes should be enough for the actual value. (largest is currently transform, which is float3 + quat + float)
|
|
||||||
byte temp_value[32];
|
|
||||||
|
|
||||||
// Un-stringize based on type.
|
|
||||||
switch (property->Type)
|
|
||||||
{
|
|
||||||
case PROPERTY_TYPE_BOOL:
|
|
||||||
if (reader.ReadUInt32() != 1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reinterpret_cast<bool&>(temp_value) = reader.ReadBool();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_UINT:
|
|
||||||
if (reader.ReadUInt32() != 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reinterpret_cast<u32&>(temp_value) = reader.ReadUInt32();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_INT:
|
|
||||||
if (reader.ReadUInt32() != 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reinterpret_cast<s32&>(temp_value) = reader.ReadInt32();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_FLOAT:
|
|
||||||
if (reader.ReadUInt32() != 4)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reinterpret_cast<float&>(temp_value) = reader.ReadFloat();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the function.
|
|
||||||
if (!property->SetPropertyCallback(object, property->pSetPropertyCallbackUserData, temp_value))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify updater if needed.
|
|
||||||
// if (pProperty->PropertyChangedCallback != NULL)
|
|
||||||
// pProperty->PropertyChangedCallback(pObject, pProperty->pPropertyChangedCallbackUserData);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EncodePropertyTypeToBuffer(PROPERTY_TYPE type, const char* value_string, BinaryWriter& writer)
|
|
||||||
{
|
|
||||||
// Strings handled seperately.
|
|
||||||
if (type == PROPERTY_TYPE_STRING)
|
|
||||||
{
|
|
||||||
// We can pass StrValue directly across.
|
|
||||||
writer.WriteUInt32(Y_strlen(value_string) + 1);
|
|
||||||
writer.WriteCString(value_string);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Now stringize it based on type.
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case PROPERTY_TYPE_BOOL:
|
|
||||||
writer.WriteUInt32(1);
|
|
||||||
writer.WriteBool(StringConverter::StringToBool(value_string));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_UINT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteUInt32(StringConverter::StringToUInt32(value_string));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_INT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteInt32(StringConverter::StringToInt32(value_string));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PROPERTY_TYPE_FLOAT:
|
|
||||||
writer.WriteUInt32(4);
|
|
||||||
writer.WriteFloat(StringConverter::StringToFloat(value_string));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
UnreachableCode();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default property callbacks
|
|
||||||
bool DefaultPropertyTableCallbacks::GetBool(const void* pObjectPtr, const void* pUserData, bool* pValuePtr)
|
|
||||||
{
|
|
||||||
*pValuePtr = *((const bool*)((((const byte*)pObjectPtr) + (*(int*)&pUserData))));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::SetBool(void* pObjectPtr, const void* pUserData, const bool* pValuePtr)
|
|
||||||
{
|
|
||||||
*((bool*)((((byte*)pObjectPtr) + (*(int*)&pUserData)))) = *pValuePtr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::GetUInt(const void* pObjectPtr, const void* pUserData, u32* pValuePtr)
|
|
||||||
{
|
|
||||||
*pValuePtr = *((const u32*)((((const byte*)pObjectPtr) + (*(u32*)&pUserData))));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::SetUInt(void* pObjectPtr, const void* pUserData, const u32* pValuePtr)
|
|
||||||
{
|
|
||||||
*((u32*)((((byte*)pObjectPtr) + (*(u32*)&pUserData)))) = *pValuePtr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::GetInt(const void* pObjectPtr, const void* pUserData, s32* pValuePtr)
|
|
||||||
{
|
|
||||||
*pValuePtr = *((const s32*)((((const byte*)pObjectPtr) + (*(s32*)&pUserData))));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::SetInt(void* pObjectPtr, const void* pUserData, const s32* pValuePtr)
|
|
||||||
{
|
|
||||||
*((s32*)((((byte*)pObjectPtr) + (*(s32*)&pUserData)))) = *pValuePtr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::GetFloat(const void* pObjectPtr, const void* pUserData, float* pValuePtr)
|
|
||||||
{
|
|
||||||
*pValuePtr = *((const float*)((((const byte*)pObjectPtr) + (*(int*)&pUserData))));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::SetFloat(void* pObjectPtr, const void* pUserData, const float* pValuePtr)
|
|
||||||
{
|
|
||||||
*((float*)((((byte*)pObjectPtr) + (*(int*)&pUserData)))) = *pValuePtr;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::SetString(void* pObjectPtr, const void* pUserData, const String* pValuePtr)
|
|
||||||
{
|
|
||||||
((String*)((((byte*)pObjectPtr) + (*(int*)&pUserData))))->Assign(*pValuePtr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::GetString(const void* pObjectPtr, const void* pUserData, String* pValuePtr)
|
|
||||||
{
|
|
||||||
pValuePtr->Assign(*((const String*)((((const byte*)pObjectPtr) + (*(int*)&pUserData)))));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DefaultPropertyTableCallbacks::GetConstBool(const void* pObjectPtr, const void* pUserData, bool* pValuePtr)
|
|
||||||
{
|
|
||||||
bool Value = (pUserData != 0) ? true : false;
|
|
||||||
*pValuePtr = Value;
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/types.h"
|
|
||||||
|
|
||||||
class BinaryReader;
|
|
||||||
class BinaryWriter;
|
|
||||||
class String;
|
|
||||||
|
|
||||||
enum : u32
|
|
||||||
{
|
|
||||||
MAX_PROPERTY_TABLE_NAME_LENGTH = 128,
|
|
||||||
MAX_PROPERTY_NAME_LENGTH = 128
|
|
||||||
};
|
|
||||||
|
|
||||||
enum PROPERTY_TYPE
|
|
||||||
{
|
|
||||||
PROPERTY_TYPE_BOOL,
|
|
||||||
PROPERTY_TYPE_UINT,
|
|
||||||
PROPERTY_TYPE_INT,
|
|
||||||
PROPERTY_TYPE_FLOAT,
|
|
||||||
PROPERTY_TYPE_STRING,
|
|
||||||
PROPERTY_TYPE_COUNT,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum PROPERTY_FLAG
|
|
||||||
{
|
|
||||||
PROPERTY_FLAG_READ_ONLY = (1 << 0), // Property cannot be modified by user. Engine can still modify it, however.
|
|
||||||
PROPERTY_FLAG_INVOKE_CHANGE_CALLBACK_ON_CREATE =
|
|
||||||
(1 << 1), // Property change callback will be invoked when the object is being created. By default it is not.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PROPERTY_DECLARATION
|
|
||||||
{
|
|
||||||
typedef bool (*GET_PROPERTY_CALLBACK)(const void* object, const void* userdata, void* value_ptr);
|
|
||||||
typedef bool (*SET_PROPERTY_CALLBACK)(void* object, const void* userdata, const void* value_ptr);
|
|
||||||
typedef void (*PROPERTY_CHANGED_CALLBACK)(void* object, const void* userdata);
|
|
||||||
|
|
||||||
const char* Name;
|
|
||||||
PROPERTY_TYPE Type;
|
|
||||||
u32 Flags;
|
|
||||||
|
|
||||||
GET_PROPERTY_CALLBACK GetPropertyCallback;
|
|
||||||
const void* pGetPropertyCallbackUserData;
|
|
||||||
SET_PROPERTY_CALLBACK SetPropertyCallback;
|
|
||||||
const void* pSetPropertyCallbackUserData;
|
|
||||||
PROPERTY_CHANGED_CALLBACK PropertyChangedCallback;
|
|
||||||
const void* pPropertyChangedCallbackUserData;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool GetPropertyValueAsString(const void* object, const PROPERTY_DECLARATION* property, String& value);
|
|
||||||
bool SetPropertyValueFromString(void* object, const PROPERTY_DECLARATION* property, const char* value);
|
|
||||||
bool WritePropertyValueToBuffer(const void* object, const PROPERTY_DECLARATION* property, BinaryWriter& writer);
|
|
||||||
bool ReadPropertyValueFromBuffer(void* object, const PROPERTY_DECLARATION* property, BinaryReader& reader);
|
|
||||||
bool EncodePropertyTypeToBuffer(PROPERTY_TYPE type, const char* value_string, BinaryWriter& writer);
|
|
||||||
|
|
||||||
namespace DefaultPropertyTableCallbacks {
|
|
||||||
// builtin functions
|
|
||||||
bool GetBool(const void* object, const void* userdata, bool* value_ptr);
|
|
||||||
bool SetBool(void* object, const void* userdata, const bool* value_ptr);
|
|
||||||
bool GetUInt(const void* object, const void* userdata, u32* value_ptr);
|
|
||||||
bool SetUInt(void* object, const void* userdata, const u32* value_ptr);
|
|
||||||
bool GetInt(const void* object, const void* userdata, s32* value_ptr);
|
|
||||||
bool SetInt(void* object, const void* userdata, const s32* value_ptr);
|
|
||||||
bool GetFloat(const void* object, const void* userdata, float* value_ptr);
|
|
||||||
bool SetFloat(void* object, const void* userdata, const float* value_ptr);
|
|
||||||
bool GetString(const void* object, const void* userdata, String* value_ptr);
|
|
||||||
bool SetString(void* object, const void* userdata, const String* value_ptr);
|
|
||||||
|
|
||||||
// static bool value
|
|
||||||
bool GetConstBool(const void* object, const void* userdata, bool* value_ptr);
|
|
||||||
} // namespace DefaultPropertyTableCallbacks
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER(Name, Type, Flags, GetPropertyCallback, GetPropertyCallbackUserData, \
|
|
||||||
SetPropertyCallback, SetPropertyCallbackUserData, PropertyChangedCallback, \
|
|
||||||
PropertyChangedCallbackUserData) \
|
|
||||||
{Name, \
|
|
||||||
Type, \
|
|
||||||
Flags, \
|
|
||||||
(PROPERTY_DECLARATION::GET_PROPERTY_CALLBACK)(GetPropertyCallback), \
|
|
||||||
(const void*)(GetPropertyCallbackUserData), \
|
|
||||||
(PROPERTY_DECLARATION::SET_PROPERTY_CALLBACK)(SetPropertyCallback), \
|
|
||||||
(const void*)(SetPropertyCallbackUserData), \
|
|
||||||
(PROPERTY_DECLARATION::PROPERTY_CHANGED_CALLBACK)(PropertyChangedCallback), \
|
|
||||||
(const void*)(PropertyChangedCallbackUserData)},
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER_BOOL(Name, Flags, Offset, ChangedFunc, ChangedFuncUserData) \
|
|
||||||
PROPERTY_TABLE_MEMBER(Name, PROPERTY_TYPE_BOOL, Flags, DefaultPropertyTableCallbacks::GetBool, (Offset), \
|
|
||||||
DefaultPropertyTableCallbacks::SetBool, (Offset), ChangedFunc, ChangedFuncUserData)
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER_UINT(Name, Flags, Offset, ChangedFunc, ChangedFuncUserData) \
|
|
||||||
PROPERTY_TABLE_MEMBER(Name, PROPERTY_TYPE_INT, Flags, DefaultPropertyTableCallbacks::GetUInt, (Offset), \
|
|
||||||
DefaultPropertyTableCallbacks::SetUInt, (Offset), ChangedFunc, ChangedFuncUserData)
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER_INT(Name, Flags, Offset, ChangedFunc, ChangedFuncUserData) \
|
|
||||||
PROPERTY_TABLE_MEMBER(Name, PROPERTY_TYPE_INT, Flags, DefaultPropertyTableCallbacks::GetInt, (Offset), \
|
|
||||||
DefaultPropertyTableCallbacks::SetInt, (Offset), ChangedFunc, ChangedFuncUserData)
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER_FLOAT(Name, Flags, Offset, ChangedFunc, ChangedFuncUserData) \
|
|
||||||
PROPERTY_TABLE_MEMBER(Name, PROPERTY_TYPE_FLOAT, Flags, DefaultPropertyTableCallbacks::GetFloat, (Offset), \
|
|
||||||
DefaultPropertyTableCallbacks::SetFloat, (Offset), ChangedFunc, ChangedFuncUserData)
|
|
||||||
|
|
||||||
#define PROPERTY_TABLE_MEMBER_STRING(Name, Flags, Offset, ChangedFunc, ChangedFuncUserData) \
|
|
||||||
PROPERTY_TABLE_MEMBER(Name, PROPERTY_TYPE_STRING, Flags, DefaultPropertyTableCallbacks::GetString, (Offset), \
|
|
||||||
DefaultPropertyTableCallbacks::SetString, (Offset), ChangedFunc, ChangedFuncUserData)
|
|
|
@ -1,110 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "YBaseLib/CString.h"
|
|
||||||
#include "YBaseLib/MemArray.h"
|
|
||||||
#include "YBaseLib/PODArray.h"
|
|
||||||
#include "common/types.h"
|
|
||||||
|
|
||||||
#define INVALID_TYPE_INDEX 0xFFFFFFFF
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
class TypeRegistry
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct RegisteredTypeInfo
|
|
||||||
{
|
|
||||||
T* pTypeInfo;
|
|
||||||
const char* TypeName;
|
|
||||||
u32 InheritanceDepth;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
TypeRegistry() {}
|
|
||||||
~TypeRegistry() {}
|
|
||||||
|
|
||||||
u32 RegisterTypeInfo(T* pTypeInfo, const char* TypeName, u32 InheritanceDepth)
|
|
||||||
{
|
|
||||||
u32 Index;
|
|
||||||
DebugAssert(pTypeInfo != nullptr);
|
|
||||||
|
|
||||||
for (Index = 0; Index < m_arrTypes.GetSize(); Index++)
|
|
||||||
{
|
|
||||||
if (m_arrTypes[Index].pTypeInfo == pTypeInfo)
|
|
||||||
Panic("Attempting to register type multiple times.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Index = 0; Index < m_arrTypes.GetSize(); Index++)
|
|
||||||
{
|
|
||||||
if (m_arrTypes[Index].pTypeInfo == nullptr)
|
|
||||||
{
|
|
||||||
m_arrTypes[Index].pTypeInfo = pTypeInfo;
|
|
||||||
m_arrTypes[Index].TypeName = TypeName;
|
|
||||||
m_arrTypes[Index].InheritanceDepth = InheritanceDepth;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Index == m_arrTypes.GetSize())
|
|
||||||
{
|
|
||||||
RegisteredTypeInfo t;
|
|
||||||
t.pTypeInfo = pTypeInfo;
|
|
||||||
t.TypeName = TypeName;
|
|
||||||
t.InheritanceDepth = InheritanceDepth;
|
|
||||||
m_arrTypes.Add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
CalculateMaxInheritanceDepth();
|
|
||||||
return Index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UnregisterTypeInfo(T* pTypeInfo)
|
|
||||||
{
|
|
||||||
u32 i;
|
|
||||||
for (i = 0; i < m_arrTypes.GetSize(); i++)
|
|
||||||
{
|
|
||||||
if (m_arrTypes[i].pTypeInfo == pTypeInfo)
|
|
||||||
{
|
|
||||||
m_arrTypes[i].pTypeInfo = nullptr;
|
|
||||||
m_arrTypes[i].TypeName = nullptr;
|
|
||||||
m_arrTypes[i].InheritanceDepth = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const u32 GetNumTypes() const { return m_arrTypes.GetSize(); }
|
|
||||||
const u32 GetMaxInheritanceDepth() const { return m_iMaxInheritanceDepth; }
|
|
||||||
|
|
||||||
const RegisteredTypeInfo& GetRegisteredTypeInfoByIndex(u32 TypeIndex) const
|
|
||||||
{
|
|
||||||
return m_arrTypes.GetElement(TypeIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const T* GetTypeInfoByIndex(u32 TypeIndex) const { return m_arrTypes.GetElement(TypeIndex).pTypeInfo; }
|
|
||||||
|
|
||||||
const T* GetTypeInfoByName(const char* TypeName) const
|
|
||||||
{
|
|
||||||
for (u32 i = 0; i < m_arrTypes.GetSize(); i++)
|
|
||||||
{
|
|
||||||
if (m_arrTypes[i].pTypeInfo != nullptr && !Y_stricmp(m_arrTypes[i].TypeName, TypeName))
|
|
||||||
return m_arrTypes[i].pTypeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
typedef MemArray<RegisteredTypeInfo> TypeArray;
|
|
||||||
TypeArray m_arrTypes;
|
|
||||||
u32 m_iMaxInheritanceDepth;
|
|
||||||
|
|
||||||
void CalculateMaxInheritanceDepth()
|
|
||||||
{
|
|
||||||
u32 i;
|
|
||||||
m_iMaxInheritanceDepth = 0;
|
|
||||||
|
|
||||||
for (i = 0; i < m_arrTypes.GetSize(); i++)
|
|
||||||
{
|
|
||||||
if (m_arrTypes[i].pTypeInfo != nullptr)
|
|
||||||
m_iMaxInheritanceDepth = Max(m_iMaxInheritanceDepth, m_arrTypes[i].InheritanceDepth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,7 +1,5 @@
|
||||||
set(SRCS
|
set(SRCS
|
||||||
main.cpp
|
main.cpp
|
||||||
sdl_audio_mixer.cpp
|
|
||||||
sdl_audio_mixer.h
|
|
||||||
sdl_audio_stream.cpp
|
sdl_audio_stream.cpp
|
||||||
sdl_audio_stream.h
|
sdl_audio_stream.h
|
||||||
sdl_interface.cpp
|
sdl_interface.cpp
|
||||||
|
|
|
@ -53,14 +53,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="icon.cpp" />
|
<ClCompile Include="icon.cpp" />
|
||||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
|
||||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
<ClCompile Include="sdl_interface.cpp" />
|
<ClCompile Include="sdl_interface.cpp" />
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
<ClInclude Include="sdl_audio_mixer.h" />
|
|
||||||
<ClInclude Include="sdl_audio_stream.h" />
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
<ClInclude Include="sdl_interface.h" />
|
<ClInclude Include="sdl_interface.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="main.cpp" />
|
<ClCompile Include="main.cpp" />
|
||||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
|
||||||
<ClCompile Include="sdl_interface.cpp" />
|
<ClCompile Include="sdl_interface.cpp" />
|
||||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
<ClCompile Include="icon.cpp" />
|
<ClCompile Include="icon.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="sdl_audio_mixer.h" />
|
|
||||||
<ClInclude Include="sdl_interface.h" />
|
<ClInclude Include="sdl_interface.h" />
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
<ClInclude Include="sdl_audio_stream.h" />
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
|
|
|
@ -1,129 +0,0 @@
|
||||||
#include "sdl_audio_mixer.h"
|
|
||||||
#include "YBaseLib/Timer.h"
|
|
||||||
#include <SDL_audio.h>
|
|
||||||
|
|
||||||
using namespace Audio;
|
|
||||||
|
|
||||||
inline SDL_AudioFormat GetSDLAudioFormat(SampleFormat format)
|
|
||||||
{
|
|
||||||
switch (format)
|
|
||||||
{
|
|
||||||
case SampleFormat::Signed8:
|
|
||||||
return AUDIO_S8;
|
|
||||||
|
|
||||||
case SampleFormat::Unsigned8:
|
|
||||||
return AUDIO_U8;
|
|
||||||
|
|
||||||
case SampleFormat::Signed16:
|
|
||||||
return AUDIO_S16SYS;
|
|
||||||
|
|
||||||
case SampleFormat::Unsigned16:
|
|
||||||
return AUDIO_U16SYS;
|
|
||||||
|
|
||||||
case SampleFormat::Signed32:
|
|
||||||
return AUDIO_S32SYS;
|
|
||||||
|
|
||||||
case SampleFormat::Float32:
|
|
||||||
return AUDIO_F32;
|
|
||||||
}
|
|
||||||
|
|
||||||
Panic("Unhandled format");
|
|
||||||
return AUDIO_U8;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioMixer::SDLAudioMixer(SDL_AudioDeviceID device_id, float output_sample_rate)
|
|
||||||
: Mixer(output_sample_rate), m_device_id(device_id)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLAudioMixer::~SDLAudioMixer()
|
|
||||||
{
|
|
||||||
SDL_CloseAudioDevice(m_device_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<SDLAudioMixer> SDLAudioMixer::Create()
|
|
||||||
{
|
|
||||||
auto mixer = std::make_unique<SDLAudioMixer>(0, 44100.0f);
|
|
||||||
SDL_AudioSpec spec = {44100, AUDIO_F32, static_cast<Uint8>(NumOutputChannels), 0, 4096, 0, 0,
|
|
||||||
RenderCallback, mixer.get()};
|
|
||||||
SDL_AudioSpec obtained_spec;
|
|
||||||
SDL_AudioDeviceID device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, 0);
|
|
||||||
if (device_id == 0)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
mixer->m_device_id = device_id;
|
|
||||||
|
|
||||||
SDL_PauseAudioDevice(device_id, SDL_FALSE);
|
|
||||||
|
|
||||||
return mixer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLAudioMixer::RenderSamples(Audio::OutputFormatType* buf, size_t num_samples)
|
|
||||||
{
|
|
||||||
CheckRenderBufferSize(num_samples);
|
|
||||||
std::fill_n(buf, num_samples * NumOutputChannels, 0.0f);
|
|
||||||
|
|
||||||
for (auto& channel : m_channels)
|
|
||||||
{
|
|
||||||
channel->ReadSamples(m_render_buffer.data(), num_samples);
|
|
||||||
|
|
||||||
// Don't bother mixing it if we're muted.
|
|
||||||
if (m_muted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// If the format is the same, we can just copy it as-is..
|
|
||||||
if (channel->GetChannels() == 1)
|
|
||||||
{
|
|
||||||
// Mono -> stereo
|
|
||||||
for (ssize_t idx = ssize_t(num_samples) - 1; idx >= 0; idx--)
|
|
||||||
{
|
|
||||||
float sample = m_render_buffer[idx];
|
|
||||||
m_render_buffer[idx * 2 + 0] = sample;
|
|
||||||
m_render_buffer[idx * 2 + 1] = sample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (channel->GetChannels() != NumOutputChannels)
|
|
||||||
{
|
|
||||||
SDL_AudioCVT cvt;
|
|
||||||
int err = SDL_BuildAudioCVT(&cvt, AUDIO_F32, Truncate8(channel->GetChannels()), int(m_output_sample_rate),
|
|
||||||
AUDIO_F32, Truncate8(NumOutputChannels), int(m_output_sample_rate));
|
|
||||||
if (err != 1)
|
|
||||||
Panic("Failed to set up audio conversion");
|
|
||||||
|
|
||||||
cvt.len = int(channel->GetChannels() * sizeof(float));
|
|
||||||
cvt.buf = reinterpret_cast<Uint8*>(m_render_buffer.data());
|
|
||||||
err = SDL_ConvertAudio(&cvt);
|
|
||||||
if (err != 0)
|
|
||||||
Panic("Failed to convert audio");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mix channels together.
|
|
||||||
const Audio::OutputFormatType* mix_src = reinterpret_cast<const Audio::OutputFormatType*>(m_render_buffer.data());
|
|
||||||
Audio::OutputFormatType* mix_dst = buf;
|
|
||||||
for (size_t i = 0; i < num_samples * NumOutputChannels; i++)
|
|
||||||
{
|
|
||||||
// TODO: Saturation/clamping here
|
|
||||||
*(mix_dst++) += *(mix_src++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
static FILE* fp = nullptr;
|
|
||||||
if (!fp)
|
|
||||||
fp = fopen("D:\\mixed.raw", "wb");
|
|
||||||
if (fp)
|
|
||||||
{
|
|
||||||
fwrite(buf, sizeof(float), num_samples * NumOutputChannels, fp);
|
|
||||||
fflush(fp);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLAudioMixer::RenderCallback(void* userdata, Uint8* stream, int len)
|
|
||||||
{
|
|
||||||
SDLAudioMixer* mixer = static_cast<SDLAudioMixer*>(userdata);
|
|
||||||
Audio::OutputFormatType* buf = reinterpret_cast<Audio::OutputFormatType*>(stream);
|
|
||||||
size_t num_samples = size_t(len) / NumOutputChannels / sizeof(Audio::OutputFormatType);
|
|
||||||
if (num_samples > 0)
|
|
||||||
mixer->RenderSamples(buf, num_samples);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "common/audio.h"
|
|
||||||
#include <SDL_audio.h>
|
|
||||||
|
|
||||||
class SDLAudioMixer : public Audio::Mixer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SDLAudioMixer(SDL_AudioDeviceID device_id, float output_sample_rate);
|
|
||||||
virtual ~SDLAudioMixer();
|
|
||||||
|
|
||||||
static std::unique_ptr<SDLAudioMixer> Create();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void RenderSamples(Audio::OutputFormatType* buf, size_t num_samples);
|
|
||||||
static void RenderCallback(void* userdata, Uint8* stream, int len);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SDL_AudioDeviceID m_device_id;
|
|
||||||
};
|
|
Loading…
Reference in a new issue