Remove unused common classes

This commit is contained in:
Connor McLaughlin 2019-10-20 22:28:19 +10:00
parent 9058a79184
commit b9e0c329d0
31 changed files with 0 additions and 4734 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &param);
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, &param);
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, &param);
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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(&sector_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(&sector_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.");
}
}

View 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;
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" />

View file

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

View file

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