mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-14 22:05:45 -04:00
Initial commit
This commit is contained in:
5
src/CMakeLists.txt
Normal file
5
src/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
add_subdirectory(common)
|
||||
add_subdirectory(pse)
|
||||
if(ENABLE_SDL_FRONTEND)
|
||||
add_subdirectory(pse-sdl)
|
||||
endif()
|
52
src/common/CMakeLists.txt
Normal file
52
src/common/CMakeLists.txt
Normal file
@ -0,0 +1,52 @@
|
||||
set(SRCS
|
||||
audio.cpp
|
||||
audio.h
|
||||
bitfield.h
|
||||
display.cpp
|
||||
display.h
|
||||
display_renderer.cpp
|
||||
display_renderer.h
|
||||
display_timing.cpp
|
||||
display_timing.h
|
||||
fastjmp.h
|
||||
hdd_image.cpp
|
||||
hdd_image.h
|
||||
jit_code_buffer.cpp
|
||||
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.h
|
||||
types.h
|
||||
type_registry.h
|
||||
)
|
||||
|
||||
add_library(common ${SRCS})
|
||||
|
||||
target_include_directories(common PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_include_directories(common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_link_libraries(common YBaseLib glad libsamplerate 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()
|
||||
|
372
src/common/audio.cpp
Normal file
372
src/common/audio.cpp
Normal file
@ -0,0 +1,372 @@
|
||||
#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
|
184
src/common/audio.h
Normal file
184
src/common/audio.h
Normal file
@ -0,0 +1,184 @@
|
||||
#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
|
121
src/common/bitfield.h
Normal file
121
src/common/bitfield.h
Normal file
@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
|
||||
// Disable MSVC warnings that we actually handle
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning)
|
||||
#endif
|
||||
|
||||
template<typename BackingDataType, typename DataType, unsigned BitIndex, unsigned BitCount>
|
||||
struct BitField
|
||||
{
|
||||
constexpr BitField() = default;
|
||||
#ifndef _MSC_VER
|
||||
BitField& operator=(const BitField& value) = delete;
|
||||
#endif
|
||||
|
||||
constexpr BackingDataType GetMask() const
|
||||
{
|
||||
return ((static_cast<BackingDataType>(~0)) >> (8 * sizeof(BackingDataType) - BitCount)) << BitIndex;
|
||||
}
|
||||
|
||||
operator DataType() const { return GetValue(); }
|
||||
|
||||
BitField& operator=(DataType value)
|
||||
{
|
||||
SetValue(value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataType operator++()
|
||||
{
|
||||
DataType value = GetValue() + 1;
|
||||
SetValue(value);
|
||||
return GetValue();
|
||||
}
|
||||
|
||||
DataType operator++(int)
|
||||
{
|
||||
DataType value = GetValue();
|
||||
SetValue(value + 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
DataType operator--()
|
||||
{
|
||||
DataType value = GetValue() - 1;
|
||||
SetValue(value);
|
||||
return GetValue();
|
||||
}
|
||||
|
||||
DataType operator--(int)
|
||||
{
|
||||
DataType value = GetValue();
|
||||
SetValue(value - 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
BitField& operator+=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() + rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator-=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() - rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator*=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() * rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator/=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() / rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator^=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() ^ rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator<<=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() >> rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitField& operator>>=(DataType rhs)
|
||||
{
|
||||
SetValue(GetValue() >> rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataType GetValue() const
|
||||
{
|
||||
// TODO: Handle signed types
|
||||
if (std::is_same<DataType, bool>::value)
|
||||
return static_cast<DataType>(!!((data & GetMask()) >> BitIndex));
|
||||
else
|
||||
return static_cast<DataType>((data & GetMask()) >> BitIndex);
|
||||
}
|
||||
|
||||
void SetValue(DataType value)
|
||||
{
|
||||
data &= ~GetMask();
|
||||
data |= (static_cast<BackingDataType>(value) << BitIndex) & GetMask();
|
||||
}
|
||||
|
||||
BackingDataType data;
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
19
src/common/bitfield.natvis
Normal file
19
src/common/bitfield.natvis
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<Type Name="BitField<*,bool,*,1>">
|
||||
<DisplayString><![CDATA[{((data >> $T2) & 1) != 0}]]></DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[bit index]">$T2</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
<Type Name="BitField<*,*,*,*>">
|
||||
<DisplayString><![CDATA[{((data >> $T3) & ((1 << $T4) - 1))}]]></DisplayString>
|
||||
<Expand>
|
||||
<Item Name="[bit index]">$T3</Item>
|
||||
<Item Name="[bit count]">$T4</Item>
|
||||
<Item Name="[bit mask]"><![CDATA[((1 << $T4) - 1) << $T3]]></Item>
|
||||
<Item Name="[masked bits]"><![CDATA[(data >> $T3) & (((1 << $T4) - 1) << $T3)]]></Item>
|
||||
<Item Name="[all bits]">data</Item>
|
||||
</Expand>
|
||||
</Type>
|
||||
</AutoVisualizer>
|
388
src/common/common.vcxproj
Normal file
388
src/common/common.vcxproj
Normal file
@ -0,0 +1,388 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="DebugFast|Win32">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="DebugFast|x64">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|Win32">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|x64">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="audio.h" />
|
||||
<ClInclude Include="bitfield.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="hdd_image.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="types.h" />
|
||||
<ClInclude Include="type_registry.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="audio.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="hdd_image.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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="fastjmp.asm" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\glad\glad.vcxproj">
|
||||
<Project>{43540154-9e1e-409c-834f-b84be5621388}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\libsamplerate\libsamplerate.vcxproj">
|
||||
<Project>{2f2a2b7b-60b3-478c-921e-3633b3c45c3f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\YBaseLib\Source\YBaseLib.vcxproj">
|
||||
<Project>{b56ce698-7300-4fa5-9609-942f1d05c5a2}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{EE054E08-3799-4A59-A422-18259C105FFD}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>common</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;WIN32;_DEBUGFAST;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\libsamplerate\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<Lib />
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||
</ImportGroup>
|
||||
</Project>
|
41
src/common/common.vcxproj.filters
Normal file
41
src/common/common.vcxproj.filters
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bitfield.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="state_wrapper.h" />
|
||||
</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="state_wrapper.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<MASM Include="fastjmp.asm" />
|
||||
</ItemGroup>
|
||||
</Project>
|
529
src/common/display.cpp
Normal file
529
src/common/display.cpp
Normal file
@ -0,0 +1,529 @@
|
||||
#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;
|
||||
}
|
||||
}
|
146
src/common/display.h
Normal file
146
src/common/display.h
Normal file
@ -0,0 +1,146 @@
|
||||
#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;
|
||||
};
|
196
src/common/display_renderer.cpp
Normal file
196
src/common/display_renderer.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
#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;
|
||||
}
|
72
src/common/display_renderer.h
Normal file
72
src/common/display_renderer.h
Normal file
@ -0,0 +1,72 @@
|
||||
#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;
|
||||
};
|
340
src/common/display_renderer_d3d.cpp
Normal file
340
src/common/display_renderer_d3d.cpp
Normal file
@ -0,0 +1,340 @@
|
||||
#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
|
55
src/common/display_renderer_d3d.h
Normal file
55
src/common/display_renderer_d3d.h
Normal file
@ -0,0 +1,55 @@
|
||||
#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
|
367
src/common/display_renderer_gl.cpp
Normal file
367
src/common/display_renderer_gl.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
#include "display_renderer_gl.h"
|
||||
#include "YBaseLib/Assert.h"
|
||||
#include "YBaseLib/Memory.h"
|
||||
#include "YBaseLib/String.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <glad.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
class DisplayGL : public Display
|
||||
{
|
||||
public:
|
||||
DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority);
|
||||
~DisplayGL();
|
||||
|
||||
void Render();
|
||||
|
||||
private:
|
||||
void UpdateFramebufferTexture();
|
||||
|
||||
GLuint m_framebuffer_texture_id = 0;
|
||||
|
||||
u32 m_framebuffer_texture_width = 0;
|
||||
u32 m_framebuffer_texture_height = 0;
|
||||
|
||||
std::vector<byte> m_framebuffer_texture_upload_buffer;
|
||||
};
|
||||
|
||||
DisplayGL::DisplayGL(DisplayRenderer* display_manager, const String& name, Type type, u8 priority)
|
||||
: Display(display_manager, name, type, priority)
|
||||
{
|
||||
// TODO: Customizable sampler states
|
||||
}
|
||||
|
||||
DisplayGL::~DisplayGL()
|
||||
{
|
||||
if (m_framebuffer_texture_id != 0)
|
||||
glDeleteTextures(1, &m_framebuffer_texture_id);
|
||||
}
|
||||
|
||||
void DisplayGL::Render()
|
||||
{
|
||||
if (UpdateFrontbuffer())
|
||||
UpdateFramebufferTexture();
|
||||
|
||||
if (m_framebuffer_texture_id == 0)
|
||||
return;
|
||||
|
||||
// Assumes that everything is already setup/bound.
|
||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
void DisplayGL::UpdateFramebufferTexture()
|
||||
{
|
||||
if (m_framebuffer_texture_width != m_front_buffer.width || m_framebuffer_texture_height != m_front_buffer.height)
|
||||
{
|
||||
m_framebuffer_texture_width = m_front_buffer.width;
|
||||
m_framebuffer_texture_height = m_front_buffer.height;
|
||||
|
||||
if (m_framebuffer_texture_width > 0 && m_framebuffer_texture_height > 0)
|
||||
{
|
||||
if (m_framebuffer_texture_id == 0)
|
||||
glGenTextures(1, &m_framebuffer_texture_id);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_framebuffer_texture_width, m_framebuffer_texture_height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_framebuffer_texture_id != 0)
|
||||
{
|
||||
glDeleteTextures(1, &m_framebuffer_texture_id);
|
||||
m_framebuffer_texture_id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_framebuffer_texture_id == 0)
|
||||
return;
|
||||
|
||||
const u32 upload_stride = m_framebuffer_texture_width * sizeof(u32);
|
||||
const size_t required_bytes = size_t(upload_stride) * m_framebuffer_texture_height;
|
||||
if (m_framebuffer_texture_upload_buffer.size() != required_bytes)
|
||||
m_framebuffer_texture_upload_buffer.resize(required_bytes);
|
||||
|
||||
CopyFramebufferToRGBA8Buffer(&m_front_buffer, m_framebuffer_texture_upload_buffer.data(), upload_stride);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_framebuffer_texture_id);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_framebuffer_texture_width, m_framebuffer_texture_height, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, m_framebuffer_texture_upload_buffer.data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DisplayRendererGL::DisplayRendererGL(WindowHandleType window_handle, u32 window_width, u32 window_height)
|
||||
: DisplayRenderer(window_handle, window_width, window_height)
|
||||
{
|
||||
}
|
||||
|
||||
DisplayRendererGL::~DisplayRendererGL() = default;
|
||||
|
||||
std::unique_ptr<Display> DisplayRendererGL::CreateDisplay(const char* name, Display::Type type,
|
||||
u8 priority /*= Display::DEFAULT_PRIORITY*/)
|
||||
{
|
||||
std::unique_ptr<DisplayGL> display = std::make_unique<DisplayGL>(this, name, type, priority);
|
||||
AddDisplay(display.get());
|
||||
return display;
|
||||
}
|
||||
|
||||
bool DisplayRendererGL::Initialize()
|
||||
{
|
||||
if (!DisplayRenderer::Initialize())
|
||||
return false;
|
||||
|
||||
if (!GLAD_GL_VERSION_2_0)
|
||||
{
|
||||
Panic("GL version 2.0 not loaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateQuadVAO())
|
||||
{
|
||||
Panic("Failed to create quad VAO");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateQuadProgram())
|
||||
{
|
||||
Panic("Failed to create quad program");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DisplayRendererGL::BeginFrame()
|
||||
{
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayRendererGL::RenderDisplays()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_display_lock);
|
||||
|
||||
// Setup GL state.
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glUseProgram(m_quad_program_id);
|
||||
BindQuadVAO();
|
||||
|
||||
// How many pixels do we need to render?
|
||||
u32 total_width = 0;
|
||||
for (const Display* display : m_active_displays)
|
||||
{
|
||||
auto dim = GetDisplayRenderSize(display);
|
||||
total_width += dim.first;
|
||||
}
|
||||
|
||||
// Compute the viewport bounds.
|
||||
const int window_width = int(m_window_width);
|
||||
const int window_height = std::max(1, int(m_window_height) - int(m_top_padding));
|
||||
|
||||
int viewport_x = (window_width - total_width) / 2;
|
||||
for (Display* display : m_active_displays)
|
||||
{
|
||||
auto dim = GetDisplayRenderSize(display);
|
||||
const int viewport_width = int(dim.first);
|
||||
const int viewport_height = int(dim.second);
|
||||
const int viewport_y = ((window_height - viewport_height) / 2) + m_top_padding;
|
||||
glViewport(viewport_x, m_window_height - viewport_height - viewport_y, viewport_width, viewport_height);
|
||||
static_cast<DisplayGL*>(display)->Render();
|
||||
viewport_x += dim.first;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayRendererGL::EndFrame() {}
|
||||
|
||||
void DisplayRendererGL::WindowResized(u32 window_width, u32 window_height)
|
||||
{
|
||||
DisplayRenderer::WindowResized(window_width, window_height);
|
||||
}
|
||||
|
||||
DisplayRenderer::BackendType DisplayRendererGL::GetBackendType()
|
||||
{
|
||||
return DisplayRenderer::BackendType::OpenGL;
|
||||
}
|
||||
|
||||
struct QuadVertex
|
||||
{
|
||||
float position[4];
|
||||
float texcoord[2];
|
||||
};
|
||||
|
||||
bool DisplayRendererGL::CreateQuadVAO()
|
||||
{
|
||||
static const QuadVertex vertices[4] = {{{1.0f, 1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}},
|
||||
{{-1.0f, 1.0f, 0.0f, 1.0f}, {0.0f, 0.0f}},
|
||||
{{1.0f, -1.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
|
||||
{{-1.0f, -1.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}};
|
||||
|
||||
if (GLAD_GL_VERSION_3_0 || GLAD_GL_ES_VERSION_3_0)
|
||||
{
|
||||
glGenVertexArrays(1, &m_quad_vao_id);
|
||||
glBindVertexArray(m_quad_vao_id);
|
||||
glGenBuffers(1, &m_quad_vbo_id);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
||||
reinterpret_cast<void*>(offsetof(QuadVertex, position[0])));
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
||||
reinterpret_cast<void*>(offsetof(QuadVertex, texcoord[0])));
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
glGenBuffers(1, &m_quad_vbo_id);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayRendererGL::BindQuadVAO()
|
||||
{
|
||||
if (GLAD_GL_VERSION_3_0 || GLAD_GL_ES_VERSION_3_0)
|
||||
{
|
||||
glBindVertexArray(m_quad_vao_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// old-style
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_quad_vbo_id);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
||||
reinterpret_cast<void*>(offsetof(QuadVertex, position[0])));
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadVertex),
|
||||
reinterpret_cast<void*>(offsetof(QuadVertex, texcoord[0])));
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
}
|
||||
|
||||
static std::string GenerateQuadVertexShader(const bool old_glsl)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (old_glsl)
|
||||
{
|
||||
ss << "#version 110\n";
|
||||
ss << "attribute vec4 a_position;\n";
|
||||
ss << "attribute vec2 a_texcoord;\n";
|
||||
ss << "varying vec2 v_texcoord;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "#version 130\n";
|
||||
ss << "in vec4 a_position;\n";
|
||||
ss << "in vec2 a_texcoord;\n";
|
||||
ss << "out vec2 v_texcoord;\n";
|
||||
}
|
||||
|
||||
ss << "void main() {\n";
|
||||
ss << " gl_Position = a_position;\n";
|
||||
ss << " v_texcoord = a_texcoord;\n";
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static std::string GenerateQuadFragmentShader(const bool old_glsl)
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (old_glsl)
|
||||
{
|
||||
ss << "#version 110\n";
|
||||
ss << "varying vec2 v_texcoord;\n";
|
||||
ss << "uniform sampler2D samp0;\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "#version 130\n";
|
||||
ss << "in vec2 v_texcoord;\n";
|
||||
ss << "uniform sampler2D samp0;\n";
|
||||
ss << "out vec4 ocol0;\n";
|
||||
}
|
||||
|
||||
ss << "void main() {\n";
|
||||
if (old_glsl)
|
||||
ss << " gl_FragColor = texture2D(samp0, v_texcoord);\n";
|
||||
else
|
||||
ss << " ocol0 = texture(samp0, v_texcoord);\n";
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool DisplayRendererGL::CreateQuadProgram()
|
||||
{
|
||||
const bool old_glsl = !GLAD_GL_VERSION_3_2 && !GLAD_GL_ES_VERSION_3_0;
|
||||
const std::string vs_str = GenerateQuadVertexShader(old_glsl);
|
||||
const std::string fs_str = GenerateQuadFragmentShader(old_glsl);
|
||||
const char* vs_str_ptr = vs_str.c_str();
|
||||
const GLint vs_length = static_cast<GLint>(vs_str.length());
|
||||
const char* fs_str_ptr = fs_str.c_str();
|
||||
const GLint fs_length = static_cast<GLint>(fs_str.length());
|
||||
GLint param;
|
||||
|
||||
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vs, 1, &vs_str_ptr, &vs_length);
|
||||
glCompileShader(vs);
|
||||
glGetShaderiv(vs, GL_COMPILE_STATUS, ¶m);
|
||||
if (param != GL_TRUE)
|
||||
{
|
||||
Panic("Failed to compile vertex shader.");
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fs, 1, &fs_str_ptr, &fs_length);
|
||||
glCompileShader(fs);
|
||||
glGetShaderiv(fs, GL_COMPILE_STATUS, ¶m);
|
||||
if (param != GL_TRUE)
|
||||
{
|
||||
Panic("Failed to compile fragment shader.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_quad_program_id = glCreateProgram();
|
||||
glAttachShader(m_quad_program_id, vs);
|
||||
glAttachShader(m_quad_program_id, fs);
|
||||
glBindAttribLocation(m_quad_program_id, 0, "a_position");
|
||||
glBindAttribLocation(m_quad_program_id, 1, "a_texcoord");
|
||||
if (!old_glsl)
|
||||
glBindFragDataLocation(m_quad_program_id, 0, "ocol0");
|
||||
glLinkProgram(m_quad_program_id);
|
||||
glGetProgramiv(m_quad_program_id, GL_LINK_STATUS, ¶m);
|
||||
if (param != GL_TRUE)
|
||||
{
|
||||
Panic("Failed to link program.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bind texture unit zero to the shader.
|
||||
glUseProgram(m_quad_program_id);
|
||||
GLint pos = glGetUniformLocation(m_quad_program_id, "samp0");
|
||||
if (pos >= 0)
|
||||
glUniform1i(pos, 0);
|
||||
|
||||
// Shaders are no longer needed after linking.
|
||||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
|
||||
glUseProgram(0);
|
||||
return true;
|
||||
}
|
35
src/common/display_renderer_gl.h
Normal file
35
src/common/display_renderer_gl.h
Normal file
@ -0,0 +1,35 @@
|
||||
#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;
|
||||
};
|
374
src/common/display_timing.cpp
Normal file
374
src/common/display_timing.cpp
Normal file
@ -0,0 +1,374 @@
|
||||
#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);
|
||||
}
|
144
src/common/display_timing.h
Normal file
144
src/common/display_timing.h
Normal file
@ -0,0 +1,144 @@
|
||||
#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;
|
||||
};
|
63
src/common/fastjmp.asm
Normal file
63
src/common/fastjmp.asm
Normal file
@ -0,0 +1,63 @@
|
||||
_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
|
44
src/common/fastjmp.h
Normal file
44
src/common/fastjmp.h
Normal file
@ -0,0 +1,44 @@
|
||||
#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
|
566
src/common/hdd_image.cpp
Normal file
566
src/common/hdd_image.cpp
Normal file
@ -0,0 +1,566 @@
|
||||
#include "hdd_image.h"
|
||||
#include "YBaseLib/FileSystem.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
Log_SetChannel(HDDImage);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
static constexpr u32 LOG_FILE_MAGIC = 0x89374897;
|
||||
struct LOG_FILE_HEADER
|
||||
{
|
||||
u32 magic;
|
||||
u32 sector_size;
|
||||
u64 image_size;
|
||||
u32 sector_count;
|
||||
u32 version_number;
|
||||
u8 padding[12];
|
||||
};
|
||||
static constexpr u32 STATE_MAGIC = 0x92087348;
|
||||
struct STATE_HEADER
|
||||
{
|
||||
u32 magic;
|
||||
u32 sector_size;
|
||||
u64 image_size;
|
||||
u32 sector_count;
|
||||
u32 version_number;
|
||||
u32 num_sectors_in_state;
|
||||
u8 padding[8];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static String GetLogFileName(const char* base_filename)
|
||||
{
|
||||
return String::FromFormat("%s.log", base_filename);
|
||||
}
|
||||
|
||||
static u64 GetSectorMapOffset(HDDImage::SectorIndex index)
|
||||
{
|
||||
return sizeof(LOG_FILE_HEADER) + (static_cast<u64>(index) * sizeof(HDDImage::SectorIndex));
|
||||
}
|
||||
|
||||
HDDImage::HDDImage(const std::string filename, ByteStream* base_stream, ByteStream* log_stream, u64 size,
|
||||
u32 sector_size, u32 sector_count, u32 version_number, LogSectorMap log_sector_map)
|
||||
: m_filename(std::move(filename)), m_base_stream(base_stream), m_log_stream(log_stream), m_image_size(size),
|
||||
m_sector_size(sector_size), m_sector_count(sector_count), m_version_number(version_number),
|
||||
m_log_sector_map(std::move(log_sector_map))
|
||||
{
|
||||
m_current_sector.data = std::make_unique<byte[]>(sector_size);
|
||||
}
|
||||
|
||||
HDDImage::~HDDImage()
|
||||
{
|
||||
m_base_stream->Release();
|
||||
m_log_stream->Release();
|
||||
}
|
||||
|
||||
ByteStream* HDDImage::CreateLogFile(const char* filename, bool truncate_existing, bool atomic_update, u64 image_size,
|
||||
u32 sector_size, u32& num_sectors, u32 version_number, LogSectorMap& sector_map)
|
||||
{
|
||||
if (sector_size == 0 || !Common::IsPow2(sector_size) ||
|
||||
((image_size + (sector_size - 1)) / sector_size) >= std::numeric_limits<SectorIndex>::max())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Fill sector map with zeros.
|
||||
num_sectors = static_cast<u32>((image_size + (sector_size - 1)) / sector_size);
|
||||
sector_map.resize(static_cast<size_t>(num_sectors));
|
||||
std::fill_n(sector_map.begin(), static_cast<size_t>(num_sectors), InvalidSectorNumber);
|
||||
|
||||
u32 open_flags = BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_SEEKABLE;
|
||||
if (truncate_existing)
|
||||
open_flags |= BYTESTREAM_OPEN_TRUNCATE;
|
||||
if (atomic_update)
|
||||
open_flags |= BYTESTREAM_OPEN_ATOMIC_UPDATE;
|
||||
|
||||
ByteStream* log_stream = FileSystem::OpenFile(filename, open_flags);
|
||||
if (!log_stream)
|
||||
return nullptr;
|
||||
|
||||
LOG_FILE_HEADER header = {};
|
||||
header.magic = LOG_FILE_MAGIC;
|
||||
header.sector_size = sector_size;
|
||||
header.image_size = image_size;
|
||||
header.sector_count = static_cast<u32>(num_sectors);
|
||||
header.version_number = version_number;
|
||||
|
||||
// Write header and sector map to the file.
|
||||
if (!log_stream->Write2(&header, sizeof(header)) ||
|
||||
!log_stream->Write2(sector_map.data(), static_cast<u32>(sizeof(SectorIndex) * sector_map.size())))
|
||||
{
|
||||
log_stream->Release();
|
||||
FileSystem::DeleteFile(filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Align the first sector to 4K, so we better utilize the OS's page cache.
|
||||
u64 pos = log_stream->GetPosition();
|
||||
if (!Common::IsAlignedPow2(pos, sector_size))
|
||||
{
|
||||
u64 padding_end = Common::AlignUpPow2(pos, sector_size);
|
||||
while (pos < padding_end)
|
||||
{
|
||||
u64 data = 0;
|
||||
u64 size = std::min(padding_end - pos, u64(sizeof(data)));
|
||||
if (!log_stream->Write2(&data, static_cast<u32>(size)))
|
||||
{
|
||||
log_stream->Release();
|
||||
FileSystem::DeleteFile(filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
pos += size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!log_stream->Flush())
|
||||
{
|
||||
log_stream->Release();
|
||||
FileSystem::DeleteFile(filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return log_stream;
|
||||
}
|
||||
|
||||
ByteStream* HDDImage::OpenLogFile(const char* filename, u64 image_size, u32& sector_size, u32& num_sectors,
|
||||
u32& version_number, LogSectorMap& sector_map)
|
||||
{
|
||||
ByteStream* log_stream =
|
||||
FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_SEEKABLE);
|
||||
if (!log_stream)
|
||||
return nullptr;
|
||||
|
||||
// Read in the image header.
|
||||
LOG_FILE_HEADER header;
|
||||
if (!log_stream->Read2(&header, sizeof(header)) || header.magic != LOG_FILE_MAGIC ||
|
||||
header.image_size != image_size || header.sector_size == 0 || !Common::IsPow2(header.sector_size))
|
||||
{
|
||||
Log_ErrorPrintf("Log file '%s': Invalid header", filename);
|
||||
log_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
num_sectors = static_cast<u32>(image_size / header.sector_size);
|
||||
if (num_sectors == 0 || header.sector_count != static_cast<u32>(num_sectors))
|
||||
{
|
||||
Log_ErrorPrintf("Log file '%s': Corrupted header", filename);
|
||||
log_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read in the sector map.
|
||||
sector_size = header.sector_size;
|
||||
version_number = header.version_number;
|
||||
sector_map.resize(num_sectors);
|
||||
if (!log_stream->Read2(sector_map.data(), static_cast<u32>(sizeof(SectorIndex) * num_sectors)))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read sector map from '%s'", filename);
|
||||
log_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return log_stream;
|
||||
}
|
||||
|
||||
HDDImage::SectorBuffer& HDDImage::GetSector(SectorIndex sector_index)
|
||||
{
|
||||
if (m_current_sector.sector_number == sector_index)
|
||||
return m_current_sector;
|
||||
|
||||
// Unload current sector and replace it.
|
||||
ReleaseSector(m_current_sector);
|
||||
LoadSector(m_current_sector, sector_index);
|
||||
return m_current_sector;
|
||||
}
|
||||
|
||||
void HDDImage::LoadSector(SectorBuffer& buf, SectorIndex sector_index)
|
||||
{
|
||||
Assert(sector_index != InvalidSectorNumber && sector_index < m_log_sector_map.size());
|
||||
if (m_log_sector_map[sector_index] == InvalidSectorNumber)
|
||||
LoadSectorFromImage(buf, sector_index);
|
||||
else
|
||||
LoadSectorFromLog(buf, sector_index);
|
||||
}
|
||||
|
||||
std::unique_ptr<HDDImage> HDDImage::Create(const char* filename, u64 size_in_bytes,
|
||||
u32 sector_size /*= DefaultReplaySectorSize*/)
|
||||
{
|
||||
String log_filename = GetLogFileName(filename);
|
||||
if (FileSystem::FileExists(filename) || FileSystem::FileExists(log_filename))
|
||||
return nullptr;
|
||||
|
||||
ByteStream* base_stream = FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE |
|
||||
BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_SEEKABLE);
|
||||
if (!base_stream)
|
||||
return nullptr;
|
||||
|
||||
// Write zeros to the image file.
|
||||
u64 image_size = 0;
|
||||
while (image_size < size_in_bytes)
|
||||
{
|
||||
u64 data = 0;
|
||||
const u32 to_write = static_cast<u32>(std::min(size_in_bytes - image_size, u64(sizeof(data))));
|
||||
if (!base_stream->Write2(&data, to_write))
|
||||
{
|
||||
base_stream->Release();
|
||||
FileSystem::DeleteFile(filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
image_size += to_write;
|
||||
}
|
||||
|
||||
// Create the log.
|
||||
u32 sector_count;
|
||||
LogSectorMap sector_map;
|
||||
ByteStream* log_stream =
|
||||
CreateLogFile(log_filename, false, false, image_size, sector_size, sector_count, 0, sector_map);
|
||||
if (!log_stream)
|
||||
{
|
||||
base_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::unique_ptr<HDDImage>(
|
||||
new HDDImage(filename, base_stream, log_stream, image_size, sector_size, sector_count, 0, std::move(sector_map)));
|
||||
}
|
||||
|
||||
std::unique_ptr<HDDImage> HDDImage::Open(const char* filename, u32 sector_size /* = DefaultReplaySectorSize */)
|
||||
{
|
||||
ByteStream* base_stream =
|
||||
FileSystem::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_SEEKABLE);
|
||||
if (!base_stream)
|
||||
return nullptr;
|
||||
|
||||
u64 image_size = base_stream->GetSize();
|
||||
if (image_size == 0)
|
||||
{
|
||||
base_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
String log_filename = GetLogFileName(filename);
|
||||
u32 sector_count;
|
||||
u32 version_number = 0;
|
||||
LogSectorMap sector_map;
|
||||
ByteStream* log_stream;
|
||||
if (FileSystem::FileExists(log_filename))
|
||||
{
|
||||
log_stream = OpenLogFile(log_filename, image_size, sector_size, sector_count, version_number, sector_map);
|
||||
if (!log_stream)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read log file for image '%s'.", filename);
|
||||
base_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_InfoPrintf("Log file not found for image '%s', creating.", filename);
|
||||
log_stream =
|
||||
CreateLogFile(log_filename, false, false, image_size, sector_size, sector_count, version_number, sector_map);
|
||||
if (!log_stream)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to create log file for image '%s'.", filename);
|
||||
base_stream->Release();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Log_DevPrintf("Opened image '%s' with log file '%s' (sector size %u)", filename, log_filename.GetCharArray(),
|
||||
sector_size);
|
||||
return std::unique_ptr<HDDImage>(new HDDImage(filename, base_stream, log_stream, image_size, sector_size,
|
||||
sector_count, version_number, std::move(sector_map)));
|
||||
}
|
||||
|
||||
void HDDImage::LoadSectorFromImage(SectorBuffer& buf, SectorIndex sector_index)
|
||||
{
|
||||
if (!m_base_stream->SeekAbsolute(GetFileOffset(sector_index)) || !m_base_stream->Read2(buf.data.get(), m_sector_size))
|
||||
Panic("Failed to read from base image.");
|
||||
|
||||
buf.sector_number = sector_index;
|
||||
buf.dirty = false;
|
||||
buf.in_log = false;
|
||||
}
|
||||
|
||||
void HDDImage::LoadSectorFromLog(SectorBuffer& buf, SectorIndex sector_index)
|
||||
{
|
||||
DebugAssert(sector_index < m_sector_count);
|
||||
|
||||
const SectorIndex log_sector_index = m_log_sector_map[sector_index];
|
||||
Assert(log_sector_index != InvalidSectorNumber);
|
||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(log_sector_index)) ||
|
||||
!m_log_stream->Read2(buf.data.get(), m_sector_size))
|
||||
{
|
||||
Panic("Failed to read from log file.");
|
||||
}
|
||||
|
||||
buf.sector_number = sector_index;
|
||||
buf.dirty = false;
|
||||
buf.in_log = true;
|
||||
}
|
||||
|
||||
void HDDImage::WriteSectorToLog(SectorBuffer& buf)
|
||||
{
|
||||
DebugAssert(buf.dirty && buf.sector_number < m_sector_count);
|
||||
|
||||
// Is the sector currently in the log?
|
||||
if (!buf.in_log)
|
||||
{
|
||||
Assert(m_log_sector_map[buf.sector_number] == InvalidSectorNumber);
|
||||
|
||||
// Need to allocate it in the log file.
|
||||
if (!m_log_stream->SeekToEnd())
|
||||
Panic("Failed to seek to end of log.");
|
||||
|
||||
const u64 sector_offset = m_log_stream->GetPosition();
|
||||
const SectorIndex log_sector_number = static_cast<SectorIndex>(sector_offset / m_sector_size);
|
||||
Log_DevPrintf("Allocating log sector %u to sector %u", buf.sector_number, log_sector_number);
|
||||
m_log_sector_map[buf.sector_number] = log_sector_number;
|
||||
|
||||
// Update log sector map in file.
|
||||
if (!m_log_stream->SeekAbsolute(GetSectorMapOffset(buf.sector_number)) ||
|
||||
!m_log_stream->Write2(&log_sector_number, sizeof(SectorIndex)))
|
||||
{
|
||||
Panic("Failed to update sector map in log file.");
|
||||
}
|
||||
|
||||
buf.in_log = true;
|
||||
}
|
||||
|
||||
// Write to the log.
|
||||
const SectorIndex log_sector_index = m_log_sector_map[buf.sector_number];
|
||||
Assert(log_sector_index != InvalidSectorNumber);
|
||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(log_sector_index)) ||
|
||||
!m_log_stream->Write2(buf.data.get(), m_sector_size))
|
||||
{
|
||||
Panic("Failed to write sector to log file.");
|
||||
}
|
||||
|
||||
buf.dirty = false;
|
||||
}
|
||||
|
||||
void HDDImage::ReleaseSector(SectorBuffer& buf)
|
||||
{
|
||||
// Write it to the log file if it's changed.
|
||||
if (m_current_sector.dirty)
|
||||
WriteSectorToLog(m_current_sector);
|
||||
|
||||
m_current_sector.sector_number = InvalidSectorNumber;
|
||||
}
|
||||
|
||||
void HDDImage::ReleaseAllSectors()
|
||||
{
|
||||
if (m_current_sector.sector_number == InvalidSectorNumber)
|
||||
return;
|
||||
|
||||
ReleaseSector(m_current_sector);
|
||||
}
|
||||
|
||||
void HDDImage::Read(void* buffer, u64 offset, u32 size)
|
||||
{
|
||||
Assert((offset + size) <= m_image_size);
|
||||
|
||||
byte* buf = reinterpret_cast<byte*>(buffer);
|
||||
while (size > 0)
|
||||
{
|
||||
// Find the sector that this offset lives in.
|
||||
const SectorIndex sector_index = static_cast<SectorIndex>(offset / m_sector_size);
|
||||
const u32 offset_in_sector = static_cast<u32>(offset % m_sector_size);
|
||||
const u32 size_to_read = std::min(size, m_sector_size - offset_in_sector);
|
||||
|
||||
// Load the sector, and read the sub-sector.
|
||||
const SectorBuffer& sec = GetSector(sector_index);
|
||||
std::memcpy(buf, &sec.data[offset_in_sector], size_to_read);
|
||||
buf += size_to_read;
|
||||
offset += size_to_read;
|
||||
size -= size_to_read;
|
||||
}
|
||||
}
|
||||
|
||||
void HDDImage::Write(const void* buffer, u64 offset, u32 size)
|
||||
{
|
||||
Assert((offset + size) <= m_image_size);
|
||||
|
||||
const byte* buf = reinterpret_cast<const byte*>(buffer);
|
||||
while (size > 0)
|
||||
{
|
||||
// Find the sector that this offset lives in.
|
||||
const SectorIndex sector_index = static_cast<SectorIndex>(offset / m_sector_size);
|
||||
const u32 offset_in_sector = static_cast<u32>(offset % m_sector_size);
|
||||
const u32 size_to_write = std::min(size, m_sector_size - offset_in_sector);
|
||||
|
||||
// Load the sector, and update it.
|
||||
SectorBuffer& sec = GetSector(sector_index);
|
||||
std::memcpy(&sec.data[offset_in_sector], buf, size_to_write);
|
||||
sec.dirty = true;
|
||||
buf += size_to_write;
|
||||
offset += size_to_write;
|
||||
size -= size_to_write;
|
||||
}
|
||||
}
|
||||
|
||||
bool HDDImage::LoadState(ByteStream* stream)
|
||||
{
|
||||
ReleaseAllSectors();
|
||||
|
||||
// Read header in from stream. It may not be valid.
|
||||
STATE_HEADER header;
|
||||
if (!stream->Read2(&header, sizeof(header)) || header.magic != STATE_MAGIC || header.image_size != m_image_size ||
|
||||
header.sector_size != m_sector_size || header.sector_count != m_sector_count)
|
||||
{
|
||||
Log_ErrorPrintf("Corrupted save state.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The version number could have changed, which means we committed since this state was saved.
|
||||
if (header.version_number != m_version_number)
|
||||
{
|
||||
Log_ErrorPrintf("Incorrect version number in save state (%u, should be %u), it is a stale state",
|
||||
header.version_number, m_version_number);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Okay, everything seems fine. We can now throw away the current log file, and re-write it.
|
||||
LogSectorMap new_sector_map;
|
||||
ByteStream* new_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, true, m_image_size,
|
||||
m_sector_size, m_sector_count, m_version_number, new_sector_map);
|
||||
if (!new_log_stream)
|
||||
return false;
|
||||
|
||||
// Write sectors from log.
|
||||
for (u32 i = 0; i < header.num_sectors_in_state; i++)
|
||||
{
|
||||
const SectorIndex log_sector_index = static_cast<SectorIndex>(new_log_stream->GetPosition() / m_sector_size);
|
||||
SectorIndex sector_index;
|
||||
if (!stream->Read2(§or_index, sizeof(sector_index)) || sector_index >= m_sector_count ||
|
||||
!ByteStream_CopyBytes(stream, m_sector_size, new_log_stream))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to copy new sector from save state.");
|
||||
new_log_stream->Discard();
|
||||
new_log_stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update new sector map.
|
||||
new_sector_map[sector_index] = log_sector_index;
|
||||
}
|
||||
|
||||
// Write the new sector map.
|
||||
if (!new_log_stream->SeekAbsolute(GetSectorMapOffset(0)) ||
|
||||
!new_log_stream->Write2(new_sector_map.data(), sizeof(SectorIndex) * m_sector_count))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to write new sector map from save state.");
|
||||
new_log_stream->Discard();
|
||||
new_log_stream->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Commit the stream, replacing the existing file. Then swap the pointers, since we may as well use the existing one.
|
||||
m_log_stream->Release();
|
||||
new_log_stream->Flush();
|
||||
new_log_stream->Commit();
|
||||
m_log_stream = new_log_stream;
|
||||
m_log_sector_map = std::move(new_sector_map);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HDDImage::SaveState(ByteStream* stream)
|
||||
{
|
||||
ReleaseAllSectors();
|
||||
|
||||
// Precompute how many sectors are committed to the log.
|
||||
u32 log_sector_count = 0;
|
||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
||||
{
|
||||
if (IsSectorInLog(sector_index))
|
||||
log_sector_count++;
|
||||
}
|
||||
|
||||
// Construct header.
|
||||
STATE_HEADER header = {};
|
||||
header.magic = STATE_MAGIC;
|
||||
header.sector_size = m_sector_size;
|
||||
header.image_size = m_image_size;
|
||||
header.sector_count = m_sector_count;
|
||||
header.version_number = m_version_number;
|
||||
header.num_sectors_in_state = log_sector_count;
|
||||
if (!stream->Write2(&header, sizeof(header)))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to write log header to save state.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy each sector from the replay log.
|
||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
||||
{
|
||||
if (!IsSectorInLog(sector_index))
|
||||
continue;
|
||||
|
||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(m_log_sector_map[sector_index])) ||
|
||||
!stream->Write2(§or_index, sizeof(sector_index)) ||
|
||||
!ByteStream_CopyBytes(m_log_stream, m_sector_size, stream))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to write log sector to save state.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HDDImage::Flush()
|
||||
{
|
||||
if (!m_current_sector.dirty)
|
||||
return;
|
||||
|
||||
WriteSectorToLog(m_current_sector);
|
||||
|
||||
// Ensure the stream isn't buffering.
|
||||
if (!m_log_stream->Flush())
|
||||
Panic("Failed to flush log stream.");
|
||||
}
|
||||
|
||||
void HDDImage::CommitLog()
|
||||
{
|
||||
Log_InfoPrintf("Committing log for '%s'.", m_filename.c_str());
|
||||
ReleaseAllSectors();
|
||||
|
||||
for (SectorIndex sector_index = 0; sector_index < m_sector_count; sector_index++)
|
||||
{
|
||||
if (!IsSectorInLog(sector_index))
|
||||
continue;
|
||||
|
||||
// Read log sector to buffer, then write it to the base image.
|
||||
// No need to update the log map, since we trash it anyway.
|
||||
if (!m_log_stream->SeekAbsolute(GetFileOffset(m_log_sector_map[sector_index])) ||
|
||||
!m_base_stream->SeekAbsolute(GetFileOffset(sector_index)) ||
|
||||
ByteStream_CopyBytes(m_log_stream, m_sector_size, m_base_stream) != m_sector_size)
|
||||
{
|
||||
Panic("Failed to transfer sector from log to base image.");
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the version number, to invalidate old save states.
|
||||
m_version_number++;
|
||||
|
||||
// Truncate the log, and re-create it.
|
||||
m_log_stream->Release();
|
||||
m_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, false, m_image_size, m_sector_size,
|
||||
m_sector_count, m_version_number, m_log_sector_map);
|
||||
}
|
||||
|
||||
void HDDImage::RevertLog()
|
||||
{
|
||||
Log_InfoPrintf("Reverting log for '%s'", m_filename.c_str());
|
||||
ReleaseAllSectors();
|
||||
|
||||
m_log_stream->Release();
|
||||
m_log_stream = CreateLogFile(GetLogFileName(m_filename.c_str()), true, false, m_image_size, m_sector_size,
|
||||
m_sector_count, m_version_number, m_log_sector_map);
|
||||
if (!m_log_stream)
|
||||
{
|
||||
Log_ErrorPrintf("Failed to recreate log file for image '%s'", m_filename.c_str());
|
||||
Panic("Failed to recreate log file.");
|
||||
}
|
||||
}
|
93
src/common/hdd_image.h
Normal file
93
src/common/hdd_image.h
Normal file
@ -0,0 +1,93 @@
|
||||
#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;
|
||||
};
|
70
src/common/jit_code_buffer.cpp
Normal file
70
src/common/jit_code_buffer.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "jit_code_buffer.h"
|
||||
#include "YBaseLib/Assert.h"
|
||||
|
||||
#if defined(Y_PLATFORM_WINDOWS)
|
||||
#include "YBaseLib/Windows/WindowsHeaders.h"
|
||||
#elif defined(Y_PLATFORM_LINUX) || defined(Y_PLATFORM_ANDROID)
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
JitCodeBuffer::JitCodeBuffer(size_t size)
|
||||
{
|
||||
#if defined(Y_PLATFORM_WINDOWS)
|
||||
m_code_ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
#elif defined(Y_PLATFORM_LINUX) || defined(Y_PLATFORM_ANDROID)
|
||||
m_code_ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
#else
|
||||
m_code_ptr = nullptr;
|
||||
#endif
|
||||
m_free_code_ptr = m_code_ptr;
|
||||
m_code_size = size;
|
||||
m_code_used = 0;
|
||||
|
||||
if (!m_code_ptr)
|
||||
Panic("Failed to allocate code space.");
|
||||
}
|
||||
|
||||
JitCodeBuffer::~JitCodeBuffer()
|
||||
{
|
||||
#if defined(Y_PLATFORM_WINDOWS)
|
||||
VirtualFree(m_code_ptr, m_code_size, MEM_RELEASE);
|
||||
#elif defined(Y_PLATFORM_LINUX) || defined(Y_PLATFORM_ANDROID)
|
||||
munmap(m_code_ptr, m_code_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void JitCodeBuffer::CommitCode(size_t length)
|
||||
{
|
||||
// // Function alignment?
|
||||
// size_t extra_bytes = ((length % 16) != 0) ? (16 - (length % 16)) : 0;
|
||||
// for (size_t i = 0; i < extra_bytes; i++)
|
||||
// reinterpret_cast<char*>(m_free_code_ptr)[i] = 0xCC;
|
||||
|
||||
Assert(length <= (m_code_size - m_code_used));
|
||||
m_free_code_ptr = reinterpret_cast<char*>(m_free_code_ptr) + length;
|
||||
m_code_used += length;
|
||||
}
|
||||
|
||||
void JitCodeBuffer::Reset()
|
||||
{
|
||||
#if defined(Y_PLATFORM_WINDOWS)
|
||||
FlushInstructionCache(GetCurrentProcess(), m_code_ptr, m_code_size);
|
||||
#elif defined(Y_PLATFORM_LINUX) || defined(Y_PLATFORM_ANDROID)
|
||||
// TODO
|
||||
#endif
|
||||
|
||||
m_free_code_ptr = m_code_ptr;
|
||||
m_code_used = 0;
|
||||
}
|
||||
|
||||
void JitCodeBuffer::Align(u32 alignment, u8 padding_value)
|
||||
{
|
||||
DebugAssert(Common::IsPow2(alignment));
|
||||
const size_t num_padding_bytes =
|
||||
std::min(static_cast<size_t>(Common::AlignUpPow2(reinterpret_cast<uintptr_t>(m_free_code_ptr), alignment) -
|
||||
reinterpret_cast<uintptr_t>(m_free_code_ptr)),
|
||||
GetFreeCodeSpace());
|
||||
std::memset(m_free_code_ptr, padding_value, num_padding_bytes);
|
||||
m_free_code_ptr = reinterpret_cast<char*>(m_free_code_ptr) + num_padding_bytes;
|
||||
m_code_used += num_padding_bytes;
|
||||
}
|
25
src/common/jit_code_buffer.h
Normal file
25
src/common/jit_code_buffer.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "types.h"
|
||||
|
||||
class JitCodeBuffer
|
||||
{
|
||||
public:
|
||||
JitCodeBuffer(size_t size = 64 * 1024 * 1024);
|
||||
~JitCodeBuffer();
|
||||
|
||||
void* GetFreeCodePointer() const { return m_free_code_ptr; }
|
||||
size_t GetFreeCodeSpace() const { return (m_code_size - m_code_used); }
|
||||
void CommitCode(size_t length);
|
||||
void Reset();
|
||||
|
||||
/// Adjusts the free code pointer to the specified alignment, padding with bytes.
|
||||
/// Assumes alignment is a power-of-two.
|
||||
void Align(u32 alignment, u8 padding_value);
|
||||
|
||||
private:
|
||||
void* m_code_ptr;
|
||||
void* m_free_code_ptr;
|
||||
size_t m_code_size;
|
||||
size_t m_code_used;
|
||||
};
|
||||
|
8
src/common/object.cpp
Normal file
8
src/common/object.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#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;
|
84
src/common/object.h
Normal file
84
src/common/object.h
Normal file
@ -0,0 +1,84 @@
|
||||
#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>();
|
129
src/common/object_type_info.cpp
Normal file
129
src/common/object_type_info.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#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);
|
||||
}
|
129
src/common/object_type_info.h
Normal file
129
src/common/object_type_info.h
Normal file
@ -0,0 +1,129 @@
|
||||
#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()
|
349
src/common/property.cpp
Normal file
349
src/common/property.cpp
Normal file
@ -0,0 +1,349 @@
|
||||
#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;
|
||||
}
|
103
src/common/property.h
Normal file
103
src/common/property.h
Normal file
@ -0,0 +1,103 @@
|
||||
#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)
|
77
src/common/state_wrapper.cpp
Normal file
77
src/common/state_wrapper.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "state_wrapper.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "YBaseLib/String.h"
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
Log_SetChannel(StateWrapper);
|
||||
|
||||
StateWrapper::StateWrapper(ByteStream* stream, Mode mode) : m_stream(stream), m_mode(mode) {}
|
||||
|
||||
StateWrapper::~StateWrapper() = default;
|
||||
|
||||
void StateWrapper::DoBytes(void* data, size_t length)
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(data, static_cast<u32>(length))) == true)
|
||||
std::memset(data, 0, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(data, static_cast<u32>(length));
|
||||
}
|
||||
}
|
||||
|
||||
void StateWrapper::Do(bool* value_ptr)
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
u8 value = 0;
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->ReadByte(&value);
|
||||
*value_ptr = (value != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 value = static_cast<u8>(*value_ptr);
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->WriteByte(value);
|
||||
}
|
||||
}
|
||||
|
||||
void StateWrapper::Do(std::string* value_ptr)
|
||||
{
|
||||
u32 length = static_cast<u32>(value_ptr->length());
|
||||
Do(&length);
|
||||
if (m_mode == Mode::Read)
|
||||
value_ptr->resize(length);
|
||||
DoBytes(&(*value_ptr)[0], length);
|
||||
value_ptr->resize(std::strlen(&(*value_ptr)[0]));
|
||||
}
|
||||
|
||||
void StateWrapper::Do(String* value_ptr)
|
||||
{
|
||||
u32 length = static_cast<u32>(value_ptr->GetLength());
|
||||
Do(&length);
|
||||
if (m_mode == Mode::Read)
|
||||
value_ptr->Resize(length);
|
||||
DoBytes(value_ptr->GetWriteableCharArray(), length);
|
||||
value_ptr->UpdateSize();
|
||||
}
|
||||
|
||||
bool StateWrapper::DoMarker(const char* marker)
|
||||
{
|
||||
SmallString file_value(marker);
|
||||
Do(&file_value);
|
||||
if (m_error)
|
||||
return false;
|
||||
|
||||
if (m_mode == Mode::Write || file_value.Compare(marker))
|
||||
return true;
|
||||
|
||||
Log_ErrorPrintf("Marker mismatch at offset %" PRIu64 ": found '%s' expected '%s'", m_stream->GetPosition(),
|
||||
file_value.GetCharArray(), marker);
|
||||
|
||||
return false;
|
||||
}
|
120
src/common/state_wrapper.h
Normal file
120
src/common/state_wrapper.h
Normal file
@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
#include "YBaseLib/ByteStream.h"
|
||||
#include "types.h"
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
class String;
|
||||
|
||||
class StateWrapper
|
||||
{
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Read,
|
||||
Write
|
||||
};
|
||||
|
||||
StateWrapper(ByteStream* stream, Mode mode);
|
||||
StateWrapper(const StateWrapper&) = delete;
|
||||
~StateWrapper();
|
||||
|
||||
ByteStream* GetStream() const { return m_stream; }
|
||||
bool HasError() const { return m_error; }
|
||||
bool IsReading() const { return (m_mode == Mode::Read); }
|
||||
bool IsWriting() const { return (m_mode == Mode::Write); }
|
||||
Mode GetMode() const { return m_mode; }
|
||||
|
||||
/// Overload for integral or floating-point types. Writes bytes as-is.
|
||||
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0>
|
||||
void Do(T* value_ptr)
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true)
|
||||
*value_ptr = static_cast<T>(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(value_ptr, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overload for enum types. Uses the underlying type.
|
||||
template<typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
|
||||
void Do(T* value_ptr)
|
||||
{
|
||||
using TType = std::underlying_type_t<T>;
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
TType temp;
|
||||
if (m_error || (m_error |= !m_stream->Read2(&temp, sizeof(TType))) == true)
|
||||
temp = static_cast<TType>(0);
|
||||
|
||||
*value_ptr = static_cast<T>(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
TType temp;
|
||||
std::memcpy(&temp, value_ptr, sizeof(TType));
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(&temp, sizeof(TType));
|
||||
}
|
||||
}
|
||||
|
||||
/// Overload for POD types, such as structs.
|
||||
template<typename T, std::enable_if_t<std::is_pod_v<T>, int> = 0>
|
||||
void DoPOD(T* value_ptr)
|
||||
{
|
||||
if (m_mode == Mode::Read)
|
||||
{
|
||||
if (m_error || (m_error |= !m_stream->Read2(value_ptr, sizeof(T))) == true)
|
||||
*value_ptr = {};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_error)
|
||||
m_error |= !m_stream->Write2(value_ptr, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void DoArray(T* values, size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
Do(&values[i]);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void DoPODArray(T* values, size_t count)
|
||||
{
|
||||
for (size_t i = 0; i < count; i++)
|
||||
DoPOD(&values[i]);
|
||||
}
|
||||
|
||||
void DoBytes(void* data, size_t length);
|
||||
|
||||
void Do(bool* value_ptr);
|
||||
void Do(std::string* value_ptr);
|
||||
void Do(String* value_ptr);
|
||||
|
||||
template<typename T>
|
||||
void Do(std::vector<T>* data)
|
||||
{
|
||||
u32 length = static_cast<u32>(data->size());
|
||||
Do(&length);
|
||||
if (m_mode == Mode::Read)
|
||||
data->resize(length);
|
||||
DoArray(data->data(), data->size());
|
||||
}
|
||||
|
||||
bool DoMarker(const char* marker);
|
||||
|
||||
private:
|
||||
ByteStream* m_stream;
|
||||
Mode m_mode;
|
||||
bool m_error = false;
|
||||
};
|
110
src/common/type_registry.h
Normal file
110
src/common/type_registry.h
Normal file
@ -0,0 +1,110 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
};
|
231
src/common/types.h
Normal file
231
src/common/types.h
Normal file
@ -0,0 +1,231 @@
|
||||
#pragma once
|
||||
|
||||
#include "YBaseLib/Common.h"
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
// Force inline helper
|
||||
#ifndef ALWAYS_INLINE
|
||||
#if defined(_MSC_VER)
|
||||
#define ALWAYS_INLINE __forceinline
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define ALWAYS_INLINE __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define ALWAYS_INLINE inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using s8 = int8_t;
|
||||
using u8 = uint8_t;
|
||||
using s16 = int16_t;
|
||||
using u16 = uint16_t;
|
||||
using s32 = int32_t;
|
||||
using u32 = uint32_t;
|
||||
using s64 = int64_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
// Enable use of static_assert in constexpr if
|
||||
template<class T>
|
||||
struct dependent_false : std::false_type
|
||||
{
|
||||
};
|
||||
template<int T>
|
||||
struct dependent_int_false : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
// Use a signed number for cycle counting
|
||||
using CycleCount = int64_t;
|
||||
|
||||
// Use int64 for time tracking
|
||||
using SimulationTime = int64_t;
|
||||
|
||||
// Helpers for simulation time.
|
||||
constexpr SimulationTime SecondsToSimulationTime(SimulationTime s)
|
||||
{
|
||||
return s * INT64_C(1000000000);
|
||||
}
|
||||
constexpr SimulationTime MillisecondsToSimulationTime(SimulationTime ms)
|
||||
{
|
||||
return ms * INT64_C(1000000);
|
||||
}
|
||||
constexpr SimulationTime MicrosecondsToSimulationTime(SimulationTime us)
|
||||
{
|
||||
return us * INT64_C(1000);
|
||||
}
|
||||
constexpr SimulationTime SimulationTimeToSeconds(SimulationTime s)
|
||||
{
|
||||
return s / INT64_C(1000000000);
|
||||
}
|
||||
constexpr SimulationTime SimulationTimeToMilliseconds(SimulationTime ms)
|
||||
{
|
||||
return ms / INT64_C(1000000);
|
||||
}
|
||||
constexpr SimulationTime SimulationTimeToMicroseconds(SimulationTime us)
|
||||
{
|
||||
return us / INT64_C(1000);
|
||||
}
|
||||
|
||||
// Calculates the difference between the specified timestamps, accounting for signed overflow.
|
||||
constexpr SimulationTime GetSimulationTimeDifference(SimulationTime prev, SimulationTime now)
|
||||
{
|
||||
if (prev <= now)
|
||||
return now - prev;
|
||||
else
|
||||
return (std::numeric_limits<SimulationTime>::max() - prev) + now;
|
||||
}
|
||||
|
||||
// Zero-extending helper
|
||||
template<typename TReturn, typename TValue>
|
||||
constexpr TReturn ZeroExtend(TValue value)
|
||||
{
|
||||
// auto unsigned_val = static_cast<typename std::make_unsigned<TValue>::type>(value);
|
||||
// auto extended_val = static_cast<typename std::make_unsigned<TReturn>::type>(unsigned_val);
|
||||
// return static_cast<TReturn>(extended_val);
|
||||
return static_cast<TReturn>(static_cast<typename std::make_unsigned<TReturn>::type>(
|
||||
static_cast<typename std::make_unsigned<TValue>::type>(value)));
|
||||
}
|
||||
// Sign-extending helper
|
||||
template<typename TReturn, typename TValue>
|
||||
constexpr TReturn SignExtend(TValue value)
|
||||
{
|
||||
// auto signed_val = static_cast<typename std::make_signed<TValue>::type>(value);
|
||||
// auto extended_val = static_cast<typename std::make_signed<TReturn>::type>(signed_val);
|
||||
// return static_cast<TReturn>(extended_val);
|
||||
return static_cast<TReturn>(
|
||||
static_cast<typename std::make_signed<TReturn>::type>(static_cast<typename std::make_signed<TValue>::type>(value)));
|
||||
}
|
||||
|
||||
// Type-specific helpers
|
||||
template<typename TValue>
|
||||
constexpr u16 ZeroExtend16(TValue value)
|
||||
{
|
||||
return ZeroExtend<u16, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u32 ZeroExtend32(TValue value)
|
||||
{
|
||||
return ZeroExtend<u32, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u64 ZeroExtend64(TValue value)
|
||||
{
|
||||
return ZeroExtend<u64, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u16 SignExtend16(TValue value)
|
||||
{
|
||||
return SignExtend<u16, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u32 SignExtend32(TValue value)
|
||||
{
|
||||
return SignExtend<u32, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u64 SignExtend64(TValue value)
|
||||
{
|
||||
return SignExtend<u64, TValue>(value);
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u8 Truncate8(TValue value)
|
||||
{
|
||||
return static_cast<u8>(static_cast<typename std::make_unsigned<decltype(value)>::type>(value));
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u16 Truncate16(TValue value)
|
||||
{
|
||||
return static_cast<u16>(static_cast<typename std::make_unsigned<decltype(value)>::type>(value));
|
||||
}
|
||||
template<typename TValue>
|
||||
constexpr u32 Truncate32(TValue value)
|
||||
{
|
||||
return static_cast<u32>(static_cast<typename std::make_unsigned<decltype(value)>::type>(value));
|
||||
}
|
||||
|
||||
// BCD helpers
|
||||
inline u8 DecimalToBCD(u8 value)
|
||||
{
|
||||
return ((value / 10) << 4) + (value % 10);
|
||||
}
|
||||
|
||||
inline u8 BCDToDecimal(u8 value)
|
||||
{
|
||||
return ((value >> 4) * 10) + (value % 16);
|
||||
}
|
||||
|
||||
// Boolean to integer
|
||||
constexpr u8 BoolToUInt8(bool value)
|
||||
{
|
||||
return static_cast<u8>(value);
|
||||
}
|
||||
constexpr u16 BoolToUInt16(bool value)
|
||||
{
|
||||
return static_cast<u16>(value);
|
||||
}
|
||||
constexpr u32 BoolToUInt32(bool value)
|
||||
{
|
||||
return static_cast<u32>(value);
|
||||
}
|
||||
constexpr u64 BoolToUInt64(bool value)
|
||||
{
|
||||
return static_cast<u64>(value);
|
||||
}
|
||||
|
||||
// Integer to boolean
|
||||
template<typename TValue>
|
||||
constexpr bool ConvertToBool(TValue value)
|
||||
{
|
||||
return static_cast<bool>(value);
|
||||
}
|
||||
|
||||
// Unsafe integer to boolean
|
||||
template<typename TValue>
|
||||
constexpr bool ConvertToBoolUnchecked(TValue value)
|
||||
{
|
||||
// static_assert(sizeof(uint8) == sizeof(bool));
|
||||
bool ret;
|
||||
std::memcpy(&ret, &value, sizeof(bool));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Enum class bitwise operators
|
||||
#define IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(type_) \
|
||||
inline constexpr type_ operator&(type_ lhs, type_ rhs) \
|
||||
{ \
|
||||
return static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) & \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
} \
|
||||
inline constexpr type_ operator|(type_ lhs, type_ rhs) \
|
||||
{ \
|
||||
return static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) | \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
} \
|
||||
inline constexpr type_ operator^(type_ lhs, type_ rhs) \
|
||||
{ \
|
||||
return static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) ^ \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
} \
|
||||
inline constexpr type_ operator~(type_ val) \
|
||||
{ \
|
||||
return static_cast<type_>(~static_cast<std::underlying_type<type_>::type>(val)); \
|
||||
} \
|
||||
inline constexpr type_& operator&=(type_& lhs, type_ rhs) \
|
||||
{ \
|
||||
lhs = static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) & \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
return lhs; \
|
||||
} \
|
||||
inline constexpr type_& operator|=(type_& lhs, type_ rhs) \
|
||||
{ \
|
||||
lhs = static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) | \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
return lhs; \
|
||||
} \
|
||||
inline constexpr type_& operator^=(type_& lhs, type_ rhs) \
|
||||
{ \
|
||||
lhs = static_cast<type_>(static_cast<std::underlying_type<type_>::type>(lhs) ^ \
|
||||
static_cast<std::underlying_type<type_>::type>(rhs)); \
|
||||
return lhs; \
|
||||
}
|
11
src/pse-sdl/CMakeLists.txt
Normal file
11
src/pse-sdl/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
set(SRCS
|
||||
main.cpp
|
||||
sdl_audio_mixer.cpp
|
||||
sdl_audio_mixer.h
|
||||
sdl_interface.cpp
|
||||
sdl_interface.h
|
||||
)
|
||||
|
||||
add_executable(pse-sdl ${SRCS})
|
||||
target_include_directories(pse-sdl PRIVATE "${SDL2_INCLUDE_DIRS}")
|
||||
target_link_libraries(pse-sdl pse imgui "${SDL2_LIBRARIES}")
|
84
src/pse-sdl/main.cpp
Normal file
84
src/pse-sdl/main.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include "YBaseLib/Assert.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "YBaseLib/StringConverter.h"
|
||||
#include "sdl_interface.h"
|
||||
#include "pse/types.h"
|
||||
#include "pse/system.h"
|
||||
#include <SDL.h>
|
||||
#include <cstdio>
|
||||
|
||||
static int NoGUITest()
|
||||
{
|
||||
std::unique_ptr<System> system = std::make_unique<System>();
|
||||
if (!system->Initialize())
|
||||
return -1;
|
||||
|
||||
system->Reset();
|
||||
|
||||
while (true)
|
||||
system->RunFrame();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int Run(int argc, char* argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::fprintf(stderr, "Usage: %s <path to system ini> [save state index]\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// init sdl
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
|
||||
{
|
||||
Panic("SDL initialization failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// create display and host interface
|
||||
std::unique_ptr<SDLInterface> host_interface = SDLInterface::Create();
|
||||
if (!host_interface)
|
||||
{
|
||||
Panic("Failed to create host interface");
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// create system
|
||||
s32 state_index = -1;
|
||||
if (argc > 2)
|
||||
state_index = StringConverter::StringToInt32(argv[2]);
|
||||
if (!host_interface->InitializeSystem(argv[1], state_index))
|
||||
{
|
||||
host_interface.reset();
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// run
|
||||
host_interface->Run();
|
||||
|
||||
// done
|
||||
host_interface.reset();
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SDL requires the entry point declared without c++ decoration
|
||||
#undef main
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
// set log flags
|
||||
g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||
|
||||
#ifdef Y_BUILD_CONFIG_RELEASE
|
||||
g_pLog->SetFilterLevel(LOGLEVEL_INFO);
|
||||
// g_pLog->SetFilterLevel(LOGLEVEL_PROFILE);
|
||||
#else
|
||||
// g_pLog->SetFilterLevel(LOGLEVEL_TRACE);
|
||||
g_pLog->SetFilterLevel(LOGLEVEL_DEBUG);
|
||||
#endif
|
||||
|
||||
return NoGUITest();
|
||||
//return Run(argc, argv);
|
||||
}
|
378
src/pse-sdl/pse-sdl.vcxproj
Normal file
378
src/pse-sdl/pse-sdl.vcxproj
Normal file
@ -0,0 +1,378 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="DebugFast|Win32">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="DebugFast|x64">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|Win32">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|x64">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\imgui\imgui.vcxproj">
|
||||
<Project>{bb08260f-6fbc-46af-8924-090ee71360c6}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\YBaseLib\Source\YBaseLib.vcxproj">
|
||||
<Project>{b56ce698-7300-4fa5-9609-942f1d05c5a2}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\common\common.vcxproj">
|
||||
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\pse\pse.vcxproj">
|
||||
<Project>{868b98c8-65a1-494b-8346-250a73a48c0a}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
||||
<ClCompile Include="sdl_interface.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="sdl_audio_mixer.h" />
|
||||
<ClInclude Include="sdl_interface.h" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{DAA8F93D-9C17-4DE2-BD0B-57891E0FF0D9}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>pse-sdl</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)bin\$(Platform)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\msvc\include\SDL;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\nativefiledialog\include;$(SolutionDir)dep\glad\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\msvc\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
12
src/pse-sdl/pse-sdl.vcxproj.filters
Normal file
12
src/pse-sdl/pse-sdl.vcxproj.filters
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="sdl_audio_mixer.cpp" />
|
||||
<ClCompile Include="sdl_interface.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="sdl_audio_mixer.h" />
|
||||
<ClInclude Include="sdl_interface.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
129
src/pse-sdl/sdl_audio_mixer.cpp
Normal file
129
src/pse-sdl/sdl_audio_mixer.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#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);
|
||||
}
|
19
src/pse-sdl/sdl_audio_mixer.h
Normal file
19
src/pse-sdl/sdl_audio_mixer.h
Normal file
@ -0,0 +1,19 @@
|
||||
#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;
|
||||
};
|
579
src/pse-sdl/sdl_interface.cpp
Normal file
579
src/pse-sdl/sdl_interface.cpp
Normal file
@ -0,0 +1,579 @@
|
||||
#include "sdl_interface.h"
|
||||
#include "YBaseLib/ByteStream.h"
|
||||
#include "YBaseLib/Error.h"
|
||||
#include "common/display_renderer_d3d.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include "imgui_impl_sdl.h"
|
||||
#include "pse/system.h"
|
||||
#include <SDL.h>
|
||||
#include <cinttypes>
|
||||
#include <glad.h>
|
||||
#ifdef Y_COMPILER_MSVC
|
||||
#include "imgui_impl_dx11.h"
|
||||
#include <SDL_syswm.h>
|
||||
#endif
|
||||
|
||||
SDLInterface::SDLInterface(SDL_Window* window, std::unique_ptr<DisplayRenderer> display_renderer,
|
||||
std::unique_ptr<MixerType> mixer)
|
||||
: m_window(window), m_display_renderer(std::move(display_renderer)), m_mixer(std::move(mixer))
|
||||
{
|
||||
}
|
||||
|
||||
SDLInterface::~SDLInterface()
|
||||
{
|
||||
m_mixer.reset();
|
||||
|
||||
switch (m_display_renderer->GetBackendType())
|
||||
{
|
||||
#ifdef Y_COMPILER_MSVC
|
||||
case DisplayRenderer::BackendType::Direct3D:
|
||||
{
|
||||
ImGui_ImplDX11_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
m_display_renderer.reset();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case DisplayRenderer::BackendType::OpenGL:
|
||||
{
|
||||
SDL_GLContext context = SDL_GL_GetCurrentContext();
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
m_display_renderer.reset();
|
||||
SDL_GL_MakeCurrent(nullptr, nullptr);
|
||||
SDL_GL_DeleteContext(context);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
|
||||
ImGui::DestroyContext();
|
||||
m_display_renderer.reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SDLInterface> SDLInterface::Create(
|
||||
DisplayRenderer::BackendType display_renderer_backend /* = DisplayRenderer::GetDefaultBackendType() */)
|
||||
{
|
||||
constexpr u32 DEFAULT_WINDOW_WIDTH = 900;
|
||||
constexpr u32 DEFAULT_WINDOW_HEIGHT = 700;
|
||||
constexpr u32 MAIN_MENU_BAR_HEIGHT = 20;
|
||||
|
||||
// Create window.
|
||||
u32 window_flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
if (display_renderer_backend == DisplayRenderer::BackendType::OpenGL)
|
||||
window_flags |= SDL_WINDOW_OPENGL;
|
||||
|
||||
auto window = std::unique_ptr<SDL_Window, void (*)(SDL_Window*)>(
|
||||
SDL_CreateWindow("PCE - Initializing...", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, DEFAULT_WINDOW_WIDTH,
|
||||
DEFAULT_WINDOW_HEIGHT, window_flags),
|
||||
[](SDL_Window* win) { SDL_DestroyWindow(win); });
|
||||
if (!window)
|
||||
{
|
||||
Panic("Failed to create window");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DisplayRenderer::WindowHandleType window_handle = nullptr;
|
||||
if (display_renderer_backend == DisplayRenderer::BackendType::OpenGL)
|
||||
{
|
||||
// We need a GL context. TODO: Move this to common.
|
||||
SDL_GLContext gl_context = SDL_GL_CreateContext(window.get());
|
||||
if (!gl_context || SDL_GL_MakeCurrent(window.get(), gl_context) != 0 || !gladLoadGL())
|
||||
{
|
||||
Panic("Failed to create GL context");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#ifdef Y_COMPILER_MSVC
|
||||
if (display_renderer_backend == DisplayRenderer::BackendType::Direct3D)
|
||||
{
|
||||
// Get window handle from SDL window
|
||||
SDL_SysWMinfo info = {};
|
||||
SDL_VERSION(&info.version);
|
||||
if (!SDL_GetWindowWMInfo(window.get(), &info))
|
||||
{
|
||||
Panic("SDL_GetWindowWMInfo failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
window_handle = info.info.win.window;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create renderer.
|
||||
auto display_renderer =
|
||||
DisplayRenderer::Create(display_renderer_backend, window_handle, DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
|
||||
if (!display_renderer)
|
||||
{
|
||||
Panic("Failed to create display");
|
||||
return nullptr;
|
||||
}
|
||||
display_renderer->SetTopPadding(MAIN_MENU_BAR_HEIGHT);
|
||||
|
||||
// Create audio renderer.
|
||||
auto mixer = MixerType::Create();
|
||||
if (!mixer)
|
||||
{
|
||||
Panic("Failed to create audio mixer");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize imgui.
|
||||
ImGui::CreateContext();
|
||||
ImGui::GetIO().IniFilename = nullptr;
|
||||
|
||||
switch (display_renderer->GetBackendType())
|
||||
{
|
||||
#ifdef Y_COMPILER_MSVC
|
||||
case DisplayRenderer::BackendType::Direct3D:
|
||||
{
|
||||
if (!ImGui_ImplSDL2_InitForD3D(window.get()) ||
|
||||
!ImGui_ImplDX11_Init(static_cast<DisplayRendererD3D*>(display_renderer.get())->GetD3DDevice(),
|
||||
static_cast<DisplayRendererD3D*>(display_renderer.get())->GetD3DContext()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window.get());
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case DisplayRenderer::BackendType::OpenGL:
|
||||
{
|
||||
if (!ImGui_ImplSDL2_InitForOpenGL(window.get(), SDL_GL_GetCurrentContext()) || !ImGui_ImplOpenGL3_Init())
|
||||
return nullptr;
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame(window.get());
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return std::make_unique<SDLInterface>(window.release(), std::move(display_renderer), std::move(mixer));
|
||||
}
|
||||
|
||||
TinyString SDLInterface::GetSaveStateFilename(u32 index)
|
||||
{
|
||||
return TinyString::FromFormat("savestate_%u.bin", index);
|
||||
}
|
||||
|
||||
bool SDLInterface::InitializeSystem(const char* filename, s32 save_state_index /* = -1 */)
|
||||
{
|
||||
Error error;
|
||||
|
||||
m_system = std::make_unique<System>();
|
||||
if (!m_system->Initialize())
|
||||
{
|
||||
m_system.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (save_state_index >= 0)
|
||||
{
|
||||
// Load the save state.
|
||||
if (!HostInterface::LoadSystemState(GetSaveStateFilename(static_cast<u32>(save_state_index)), &error))
|
||||
{
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Loading save state failed", error.GetErrorCodeAndDescription(),
|
||||
m_window);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Resume execution.
|
||||
m_running = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDLInterface::ReportMessage(const char* message)
|
||||
{
|
||||
AddOSDMessage(message, 3.0f);
|
||||
}
|
||||
|
||||
bool SDLInterface::IsWindowFullscreen() const
|
||||
{
|
||||
return ((SDL_GetWindowFlags(m_window) & SDL_WINDOW_FULLSCREEN) != 0);
|
||||
}
|
||||
|
||||
static inline u32 SDLButtonToHostButton(u32 button)
|
||||
{
|
||||
// SDL left = 1, middle = 2, right = 3 :/
|
||||
switch (button)
|
||||
{
|
||||
case 1:
|
||||
return 0;
|
||||
case 2:
|
||||
return 2;
|
||||
case 3:
|
||||
return 1;
|
||||
default:
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
|
||||
{
|
||||
if (PassEventToImGui(event))
|
||||
return true;
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
#if 0
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
{
|
||||
u32 button = SDLButtonToHostButton(event->button.button);
|
||||
if (IsMouseGrabbed())
|
||||
{
|
||||
ExecuteMouseButtonChangeCallbacks(button, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
{
|
||||
u32 button = SDLButtonToHostButton(event->button.button);
|
||||
if (IsMouseGrabbed())
|
||||
{
|
||||
ExecuteMouseButtonChangeCallbacks(button, false);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Are we capturing the mouse?
|
||||
if (button == 0)
|
||||
GrabMouse();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
{
|
||||
if (!IsMouseGrabbed())
|
||||
return false;
|
||||
|
||||
s32 dx = event->motion.xrel;
|
||||
s32 dy = event->motion.yrel;
|
||||
ExecuteMousePositionChangeCallbacks(dx, dy);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
// Release mouse key combination
|
||||
if (((event->key.keysym.sym == SDLK_LCTRL || event->key.keysym.sym == SDLK_RCTRL) &&
|
||||
(SDL_GetModState() & KMOD_ALT) != 0) ||
|
||||
((event->key.keysym.sym == SDLK_LALT || event->key.keysym.sym == SDLK_RALT) &&
|
||||
(SDL_GetModState() & KMOD_CTRL) != 0))
|
||||
{
|
||||
// But don't consume the key event.
|
||||
ReleaseMouse();
|
||||
}
|
||||
|
||||
// Create keyboard event.
|
||||
// TODO: Since we have crap in the input polling, we can't return true here.
|
||||
GenScanCode scancode;
|
||||
if (MapSDLScanCode(&scancode, event->key.keysym.scancode))
|
||||
{
|
||||
ExecuteKeyboardCallbacks(scancode, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
// Create keyboard event.
|
||||
// TODO: Since we have crap in the input polling, we can't return true here.
|
||||
GenScanCode scancode;
|
||||
if (MapSDLScanCode(&scancode, event->key.keysym.scancode))
|
||||
{
|
||||
ExecuteKeyboardCallbacks(scancode, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
{
|
||||
if (event->window.event == SDL_WINDOWEVENT_RESIZED)
|
||||
m_display_renderer->WindowResized(u32(event->window.data1), u32(event->window.data2));
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_QUIT:
|
||||
m_running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDLInterface::PassEventToImGui(const SDL_Event* event)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
switch (event->type)
|
||||
{
|
||||
case SDL_MOUSEWHEEL:
|
||||
{
|
||||
if (event->wheel.x > 0)
|
||||
io.MouseWheelH += 1;
|
||||
if (event->wheel.x < 0)
|
||||
io.MouseWheelH -= 1;
|
||||
if (event->wheel.y > 0)
|
||||
io.MouseWheel += 1;
|
||||
if (event->wheel.y < 0)
|
||||
io.MouseWheel -= 1;
|
||||
return io.WantCaptureMouse;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
{
|
||||
bool down = event->type == SDL_MOUSEBUTTONDOWN;
|
||||
if (event->button.button == SDL_BUTTON_LEFT)
|
||||
io.MouseDown[0] = down;
|
||||
if (event->button.button == SDL_BUTTON_RIGHT)
|
||||
io.MouseDown[1] = down;
|
||||
if (event->button.button == SDL_BUTTON_MIDDLE)
|
||||
io.MouseDown[2] = down;
|
||||
return io.WantCaptureMouse;
|
||||
}
|
||||
|
||||
case SDL_MOUSEMOTION:
|
||||
{
|
||||
io.MousePos.x = float(event->motion.x);
|
||||
io.MousePos.y = float(event->motion.y);
|
||||
return io.WantCaptureMouse;
|
||||
}
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
{
|
||||
io.AddInputCharactersUTF8(event->text.text);
|
||||
return io.WantCaptureKeyboard;
|
||||
}
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
int key = event->key.keysym.scancode;
|
||||
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
||||
io.KeysDown[key] = (event->type == SDL_KEYDOWN);
|
||||
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
||||
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
||||
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
||||
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
||||
return io.WantCaptureKeyboard;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SDLInterface::Render()
|
||||
{
|
||||
if (!m_display_renderer->BeginFrame())
|
||||
return;
|
||||
|
||||
m_display_renderer->RenderDisplays();
|
||||
|
||||
RenderImGui();
|
||||
|
||||
const DisplayRenderer::BackendType backend_type = m_display_renderer->GetBackendType();
|
||||
switch (backend_type)
|
||||
{
|
||||
#ifdef Y_COMPILER_MSVC
|
||||
case DisplayRenderer::BackendType::Direct3D:
|
||||
{
|
||||
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
||||
m_display_renderer->EndFrame();
|
||||
ImGui_ImplSDL2_NewFrame(m_window);
|
||||
ImGui_ImplDX11_NewFrame();
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case DisplayRenderer::BackendType::OpenGL:
|
||||
{
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
m_display_renderer->EndFrame();
|
||||
SDL_GL_SwapWindow(m_window);
|
||||
ImGui_ImplSDL2_NewFrame(m_window);
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void SDLInterface::RenderImGui()
|
||||
{
|
||||
RenderMainMenuBar();
|
||||
RenderOSDMessages();
|
||||
|
||||
ImGui::Render();
|
||||
}
|
||||
|
||||
void SDLInterface::RenderMainMenuBar()
|
||||
{
|
||||
if (!ImGui::BeginMainMenuBar())
|
||||
return;
|
||||
|
||||
if (ImGui::BeginMenu("System"))
|
||||
{
|
||||
if (ImGui::MenuItem("Reset"))
|
||||
m_system->Reset();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
#if 0
|
||||
if (ImGui::MenuItem("Enable Speed Limiter", nullptr, IsSpeedLimiterEnabled()))
|
||||
SetSpeedLimiterEnabled(!IsSpeedLimiterEnabled());
|
||||
#endif
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Load State"))
|
||||
{
|
||||
for (u32 i = 1; i <= 8; i++)
|
||||
{
|
||||
if (ImGui::MenuItem(TinyString::FromFormat("State %u", i).GetCharArray()))
|
||||
DoLoadState(i);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Save State"))
|
||||
{
|
||||
for (u32 i = 1; i <= 8; i++)
|
||||
{
|
||||
if (ImGui::MenuItem(TinyString::FromFormat("State %u", i).GetCharArray()))
|
||||
DoSaveState(i);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Exit"))
|
||||
m_running = false;
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View"))
|
||||
{
|
||||
if (ImGui::MenuItem("Fullscreen", nullptr, IsWindowFullscreen()))
|
||||
SDL_SetWindowFullscreen(m_window, IsWindowFullscreen() ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
void SDLInterface::AddOSDMessage(const char* message, float duration /*= 2.0f*/)
|
||||
{
|
||||
OSDMessage msg;
|
||||
msg.text = message;
|
||||
msg.duration = duration;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
|
||||
m_osd_messages.push_back(std::move(msg));
|
||||
}
|
||||
|
||||
void SDLInterface::RenderOSDMessages()
|
||||
{
|
||||
constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
|
||||
|
||||
auto iter = m_osd_messages.begin();
|
||||
float position_x = 10.0f;
|
||||
float position_y = 10.0f + 20.0f;
|
||||
u32 index = 0;
|
||||
while (iter != m_osd_messages.end())
|
||||
{
|
||||
const OSDMessage& msg = *iter;
|
||||
const double time = msg.time.GetTimeSeconds();
|
||||
const float time_remaining = static_cast<float>(msg.duration - time);
|
||||
if (time_remaining <= 0.0f)
|
||||
{
|
||||
iter = m_osd_messages.erase(iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float opacity = std::min(time_remaining, 1.0f);
|
||||
ImGui::SetNextWindowPos(ImVec2(position_x, position_y));
|
||||
ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity);
|
||||
|
||||
if (ImGui::Begin(SmallString::FromFormat("osd_%u", index++), nullptr, window_flags))
|
||||
{
|
||||
ImGui::TextUnformatted(msg.text);
|
||||
position_y += ImGui::GetWindowSize().y + (4.0f * ImGui::GetIO().DisplayFramebufferScale.x);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInterface::DoLoadState(u32 index)
|
||||
{
|
||||
#if 0
|
||||
Error error;
|
||||
if (!LoadSystemState(TinyString::FromFormat("savestate_%u.bin", index), &error))
|
||||
{
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Loading save state failed", error.GetErrorCodeAndDescription(),
|
||||
m_window);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDLInterface::DoSaveState(u32 index)
|
||||
{
|
||||
#if 0
|
||||
SaveSystemState(TinyString::FromFormat("savestate_%u.bin", index));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SDLInterface::Run()
|
||||
{
|
||||
while (m_running)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
SDL_Event ev;
|
||||
if (SDL_PollEvent(&ev))
|
||||
HandleSDLEvent(&ev);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
m_system->RunFrame();
|
||||
Render();
|
||||
}
|
||||
}
|
||||
|
70
src/pse-sdl/sdl_interface.h
Normal file
70
src/pse-sdl/sdl_interface.h
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "common/display_renderer.h"
|
||||
#include "pse-sdl/sdl_audio_mixer.h"
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
|
||||
struct SDL_Window;
|
||||
union SDL_Event;
|
||||
|
||||
class System;
|
||||
|
||||
class SDLInterface
|
||||
{
|
||||
public:
|
||||
using MixerType = SDLAudioMixer;
|
||||
// using MixerType = Audio::NullMixer;
|
||||
|
||||
SDLInterface(SDL_Window* window, std::unique_ptr<DisplayRenderer> display_renderer,
|
||||
std::unique_ptr<MixerType> mixer);
|
||||
~SDLInterface();
|
||||
|
||||
static std::unique_ptr<SDLInterface>
|
||||
Create(DisplayRenderer::BackendType display_renderer_backend = DisplayRenderer::GetDefaultBackendType());
|
||||
|
||||
static TinyString GetSaveStateFilename(u32 index);
|
||||
|
||||
bool InitializeSystem(const char* filename, s32 save_state_index = -1);
|
||||
|
||||
DisplayRenderer* GetDisplayRenderer() const { return m_display_renderer.get(); }
|
||||
Audio::Mixer* GetAudioMixer() const { return m_mixer.get(); }
|
||||
|
||||
void ReportMessage(const char* message);
|
||||
|
||||
void Run();
|
||||
|
||||
// Adds OSD messages, duration is in seconds.
|
||||
void AddOSDMessage(const char* message, float duration = 2.0f);
|
||||
|
||||
protected:
|
||||
struct OSDMessage
|
||||
{
|
||||
String text;
|
||||
Timer time;
|
||||
float duration;
|
||||
};
|
||||
|
||||
// We only pass mouse input through if it's grabbed
|
||||
bool IsWindowFullscreen() const;
|
||||
void RenderImGui();
|
||||
void DoLoadState(u32 index);
|
||||
void DoSaveState(u32 index);
|
||||
|
||||
bool HandleSDLEvent(const SDL_Event* event);
|
||||
bool PassEventToImGui(const SDL_Event* event);
|
||||
void Render();
|
||||
void RenderMainMenuBar();
|
||||
void RenderOSDMessages();
|
||||
|
||||
SDL_Window* m_window;
|
||||
|
||||
std::unique_ptr<DisplayRenderer> m_display_renderer;
|
||||
std::unique_ptr<MixerType> m_mixer;
|
||||
std::unique_ptr<System> m_system;
|
||||
|
||||
std::deque<OSDMessage> m_osd_messages;
|
||||
std::mutex m_osd_messages_lock;
|
||||
|
||||
bool m_running = false;
|
||||
};
|
16
src/pse/CMakeLists.txt
Normal file
16
src/pse/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
||||
add_library(pse
|
||||
cpu_bus.cpp
|
||||
cpu_bus.h
|
||||
cpu_bus.inl
|
||||
cpu_core.cpp
|
||||
cpu_core.h
|
||||
cpu_core.inl
|
||||
system.cpp
|
||||
system.h
|
||||
types.h
|
||||
)
|
||||
|
||||
target_include_directories(pse PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_include_directories(pse PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||
target_link_libraries(pse Threads::Threads YBaseLib common)
|
||||
|
220
src/pse/bus.cpp
Normal file
220
src/pse/bus.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
#include "bus.h"
|
||||
#include "YBaseLib/ByteStream.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "YBaseLib/String.h"
|
||||
#include "dma.h"
|
||||
#include <cstdio>
|
||||
Log_SetChannel(Bus);
|
||||
|
||||
Bus::Bus() = default;
|
||||
|
||||
Bus::~Bus() = default;
|
||||
|
||||
bool Bus::Initialize(System* system, DMA* dma, GPU* gpu)
|
||||
{
|
||||
if (!LoadBIOS())
|
||||
return false;
|
||||
|
||||
m_dma = dma;
|
||||
m_gpu = gpu;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bus::Reset()
|
||||
{
|
||||
m_ram.fill(static_cast<u8>(0));
|
||||
}
|
||||
|
||||
bool Bus::DoState(StateWrapper& sw)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bus::ReadByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(cpu_address, bus_address, temp);
|
||||
*value = Truncate8(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bus::ReadWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result =
|
||||
DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(cpu_address, bus_address, temp);
|
||||
*value = Truncate16(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bus::ReadDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32* value)
|
||||
{
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(cpu_address, bus_address, *value);
|
||||
}
|
||||
|
||||
bool Bus::WriteByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(cpu_address, bus_address, temp);
|
||||
}
|
||||
|
||||
bool Bus::WriteWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(cpu_address, bus_address, temp);
|
||||
}
|
||||
|
||||
bool Bus::WriteDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32 value)
|
||||
{
|
||||
return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(cpu_address, bus_address, value);
|
||||
}
|
||||
|
||||
bool Bus::LoadBIOS()
|
||||
{
|
||||
std::FILE* fp = std::fopen("SCPH1001.BIN", "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
const u32 size = static_cast<u32>(std::ftell(fp));
|
||||
std::fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != m_bios.size())
|
||||
{
|
||||
Log_ErrorPrintf("BIOS image mismatch, expecting %u bytes, got %u bytes", static_cast<u32>(m_bios.size()), size);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::fread(m_bios.data(), 1, m_bios.size(), fp) != m_bios.size())
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read BIOS image");
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
#if 1
|
||||
auto Patch = [this](u32 address, u32 value) { std::memcpy(&m_bios[address], &value, sizeof(value)); };
|
||||
Patch(0x6F0C, 0x24010001); // addiu $at, $zero, 1
|
||||
Patch(0x6F14, 0xaf81a9c0); // sw at, -0x5640(gp)
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress cpu_address,
|
||||
PhysicalMemoryAddress bus_address, u32& value)
|
||||
{
|
||||
SmallString str;
|
||||
str.AppendString("Invalid bus ");
|
||||
if (size == MemoryAccessSize::Byte)
|
||||
str.AppendString("byte");
|
||||
if (size == MemoryAccessSize::HalfWord)
|
||||
str.AppendString("word");
|
||||
if (size == MemoryAccessSize::Word)
|
||||
str.AppendString("dword");
|
||||
str.AppendCharacter(' ');
|
||||
if (type == MemoryAccessType::Read)
|
||||
str.AppendString("read");
|
||||
else
|
||||
str.AppendString("write");
|
||||
|
||||
str.AppendFormattedString(" at address 0x%08X (virtual address 0x%08X)", bus_address, cpu_address);
|
||||
if (type == MemoryAccessType::Write)
|
||||
str.AppendFormattedString(" (value 0x%08X)", value);
|
||||
|
||||
Log_ErrorPrint(str);
|
||||
if (type == MemoryAccessType::Read)
|
||||
value = UINT32_C(0xFFFFFFFF);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::ReadExpansionRegion2(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
offset &= EXP2_MASK;
|
||||
|
||||
// rx/tx buffer empty
|
||||
if (offset == 0x21)
|
||||
{
|
||||
value = 0x04 | 0x08;
|
||||
return true;
|
||||
}
|
||||
|
||||
return DoInvalidAccess(MemoryAccessType::Read, size, EXP2_BASE | offset, EXP2_BASE | offset, value);
|
||||
}
|
||||
|
||||
bool Bus::WriteExpansionRegion2(MemoryAccessSize size, u32 offset, u32 value)
|
||||
{
|
||||
offset &= EXP2_MASK;
|
||||
|
||||
if (offset == 0x23)
|
||||
{
|
||||
if (value == '\r')
|
||||
return true;
|
||||
|
||||
if (value == '\n')
|
||||
{
|
||||
if (!m_tty_line_buffer.IsEmpty())
|
||||
Log_InfoPrintf("TTY: %s", m_tty_line_buffer.GetCharArray());
|
||||
m_tty_line_buffer.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tty_line_buffer.AppendCharacter(Truncate8(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (offset == 0x41)
|
||||
{
|
||||
Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F));
|
||||
return true;
|
||||
}
|
||||
|
||||
return DoInvalidAccess(MemoryAccessType::Write, size, EXP2_BASE | offset, EXP2_BASE | offset, value);
|
||||
}
|
||||
|
||||
bool Bus::ReadSPU(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
if (offset == 0x1AE)
|
||||
{
|
||||
value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//return DoInvalidAccess(MemoryAccessType::Write, size, SPU_BASE | offset, SPU_BASE | offset, value);
|
||||
value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::WriteSPU(MemoryAccessSize size, u32 offset, u32 value)
|
||||
{
|
||||
// Transfer FIFO
|
||||
if (offset == 0x1A8)
|
||||
return true;
|
||||
|
||||
// SPUCNT
|
||||
if (offset == 0x1AA)
|
||||
return true;
|
||||
|
||||
//return DoInvalidAccess(MemoryAccessType::Write, size, SPU_BASE | offset, SPU_BASE | offset, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoReadDMA(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
Assert(size == MemoryAccessSize::Word);
|
||||
value = m_dma->ReadRegister(offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoWriteDMA(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
Assert(size == MemoryAccessSize::Word);
|
||||
m_dma->WriteRegister(offset, value);
|
||||
return true;
|
||||
}
|
72
src/pse/bus.h
Normal file
72
src/pse/bus.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "YBaseLib/String.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
|
||||
class StateWrapper;
|
||||
class DMA;
|
||||
class GPU;
|
||||
class System;
|
||||
|
||||
class Bus
|
||||
{
|
||||
public:
|
||||
Bus();
|
||||
~Bus();
|
||||
|
||||
bool Initialize(System* system, DMA* dma, GPU* gpu);
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
bool ReadByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8* value);
|
||||
bool ReadWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16* value);
|
||||
bool ReadDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32* value);
|
||||
bool WriteByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8 value);
|
||||
bool WriteWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16 value);
|
||||
bool WriteDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32 value);
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32& value);
|
||||
|
||||
private:
|
||||
static constexpr u32 DMA_BASE = 0x1F801080;
|
||||
static constexpr u32 DMA_SIZE = 0x80;
|
||||
static constexpr u32 DMA_MASK = DMA_SIZE - 1;
|
||||
static constexpr u32 SPU_BASE = 0x1F801C00;
|
||||
static constexpr u32 SPU_SIZE = 0x300;
|
||||
static constexpr u32 SPU_MASK = 0x3FF;
|
||||
static constexpr u32 EXP2_BASE = 0x1F802000;
|
||||
static constexpr u32 EXP2_SIZE = 0x2000;
|
||||
static constexpr u32 EXP2_MASK = EXP2_SIZE - 1;
|
||||
|
||||
bool LoadBIOS();
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool DoRAMAccess(u32 offset, u32& value);
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool DoBIOSAccess(u32 offset, u32& value);
|
||||
|
||||
bool DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress cpu_address,
|
||||
PhysicalMemoryAddress bus_address, u32& value);
|
||||
|
||||
bool ReadExpansionRegion2(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool WriteExpansionRegion2(MemoryAccessSize size, u32 offset, u32 value);
|
||||
|
||||
bool ReadSPU(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool WriteSPU(MemoryAccessSize size, u32 offset, u32 value);
|
||||
|
||||
bool DoReadDMA(MemoryAccessSize size, u32 offset, u32& value);
|
||||
bool DoWriteDMA(MemoryAccessSize size, u32 offset, u32& value);
|
||||
|
||||
DMA* m_dma = nullptr;
|
||||
GPU* m_gpu = nullptr;
|
||||
|
||||
std::array<u8, 2097152> m_ram{}; // 2MB RAM
|
||||
std::array<u8, 524288> m_bios{}; // 512K BIOS ROM
|
||||
|
||||
String m_tty_line_buffer;
|
||||
};
|
||||
|
||||
#include "bus.inl"
|
130
src/pse/bus.inl
Normal file
130
src/pse/bus.inl
Normal file
@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
#include "bus.h"
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool Bus::DoRAMAccess(u32 offset, u32& value)
|
||||
{
|
||||
// TODO: Configurable mirroring.
|
||||
offset &= UINT32_C(0x1FFFFF);
|
||||
if constexpr (type == MemoryAccessType::Read)
|
||||
{
|
||||
if constexpr (size == MemoryAccessSize::Byte)
|
||||
{
|
||||
value = ZeroExtend32(m_ram[offset]);
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||
{
|
||||
u16 temp;
|
||||
std::memcpy(&temp, &m_ram[offset], sizeof(u16));
|
||||
value = ZeroExtend32(temp);
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::Word)
|
||||
{
|
||||
std::memcpy(&value, &m_ram[offset], sizeof(u32));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr (size == MemoryAccessSize::Byte)
|
||||
{
|
||||
m_ram[offset] = Truncate8(value);
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||
{
|
||||
const u16 temp = Truncate16(value);
|
||||
std::memcpy(&m_ram[offset], &temp, sizeof(u16));
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::Word)
|
||||
{
|
||||
std::memcpy(&m_ram[offset], &value, sizeof(u32));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool Bus::DoBIOSAccess(u32 offset, u32& value)
|
||||
{
|
||||
// TODO: Configurable mirroring.
|
||||
if constexpr (type == MemoryAccessType::Read)
|
||||
{
|
||||
offset &= UINT32_C(0x7FFFF);
|
||||
if constexpr (size == MemoryAccessSize::Byte)
|
||||
{
|
||||
value = ZeroExtend32(m_bios[offset]);
|
||||
}
|
||||
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||
{
|
||||
u16 temp;
|
||||
std::memcpy(&temp, &m_bios[offset], sizeof(u16));
|
||||
value = ZeroExtend32(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(&value, &m_bios[offset], sizeof(u32));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Writes are ignored.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size>
|
||||
bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32& value)
|
||||
{
|
||||
if (bus_address < 0x800000)
|
||||
{
|
||||
return DoRAMAccess<type, size>(bus_address, value);
|
||||
}
|
||||
else if (bus_address < DMA_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < (DMA_BASE + DMA_SIZE))
|
||||
{
|
||||
return (type == MemoryAccessType::Read) ? DoReadDMA(size, bus_address & DMA_MASK, value) :
|
||||
DoWriteDMA(size, bus_address & DMA_MASK, value);
|
||||
}
|
||||
else if (bus_address < SPU_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < (SPU_BASE + SPU_SIZE))
|
||||
{
|
||||
return (type == MemoryAccessType::Read) ? ReadSPU(size, bus_address & SPU_MASK, value) :
|
||||
WriteSPU(size, bus_address & SPU_MASK, value);
|
||||
}
|
||||
else if (bus_address < EXP2_BASE)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < (EXP2_BASE + EXP2_SIZE))
|
||||
{
|
||||
return (type == MemoryAccessType::Read) ? ReadExpansionRegion2(size, bus_address & EXP2_MASK, value) :
|
||||
WriteExpansionRegion2(size, bus_address & EXP2_MASK, value);
|
||||
}
|
||||
else if (bus_address < 0x1FC00000)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < 0x20000000)
|
||||
{
|
||||
return DoBIOSAccess<type, size>(static_cast<u32>(bus_address - UINT32_C(0x1FC00000)), value);
|
||||
}
|
||||
else if (bus_address < 0x1FFE0000)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else if (bus_address < 0x1FFE0200) // I/O Ports (Cache Control)
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
|
||||
}
|
||||
}
|
192
src/pse/cpu_bus.cpp
Normal file
192
src/pse/cpu_bus.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include "cpu_bus.h"
|
||||
#include "YBaseLib/ByteStream.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "YBaseLib/String.h"
|
||||
#include <cstdio>
|
||||
Log_SetChannel(Bus);
|
||||
|
||||
Bus::Bus() = default;
|
||||
|
||||
Bus::~Bus() = default;
|
||||
|
||||
bool Bus::Initialize(System* system)
|
||||
{
|
||||
if (!LoadBIOS())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bus::Reset()
|
||||
{
|
||||
m_ram.fill(static_cast<u8>(0));
|
||||
}
|
||||
|
||||
bool Bus::DoState(StateWrapper& sw)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bus::ReadByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result = DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(cpu_address, bus_address, temp);
|
||||
*value = Truncate8(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bus::ReadWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16* value)
|
||||
{
|
||||
u32 temp = 0;
|
||||
const bool result =
|
||||
DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(cpu_address, bus_address, temp);
|
||||
*value = Truncate16(temp);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Bus::ReadDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32* value)
|
||||
{
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(cpu_address, bus_address, *value);
|
||||
}
|
||||
|
||||
bool Bus::WriteByte(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u8 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(cpu_address, bus_address, temp);
|
||||
}
|
||||
|
||||
bool Bus::WriteWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u16 value)
|
||||
{
|
||||
u32 temp = ZeroExtend32(value);
|
||||
return DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(cpu_address, bus_address, temp);
|
||||
}
|
||||
|
||||
bool Bus::WriteDWord(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddress bus_address, u32 value)
|
||||
{
|
||||
return DispatchAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(cpu_address, bus_address, value);
|
||||
}
|
||||
|
||||
bool Bus::LoadBIOS()
|
||||
{
|
||||
std::FILE* fp = std::fopen("SCPH1001.BIN", "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
const u32 size = static_cast<u32>(std::ftell(fp));
|
||||
std::fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != m_bios.size())
|
||||
{
|
||||
Log_ErrorPrintf("BIOS image mismatch, expecting %u bytes, got %u bytes", static_cast<u32>(m_bios.size()), size);
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::fread(m_bios.data(), 1, m_bios.size(), fp) != m_bios.size())
|
||||
{
|
||||
Log_ErrorPrintf("Failed to read BIOS image");
|
||||
std::fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
#if 1
|
||||
auto Patch = [this](u32 address, u32 value) { std::memcpy(&m_bios[address], &value, sizeof(value)); };
|
||||
Patch(0x6F0C, 0x24010001); // addiu $at, $zero, 1
|
||||
Patch(0x6F14, 0xaf81a9c0); // sw at, -0x5640(gp)
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::DoInvalidAccess(MemoryAccessType type, MemoryAccessSize size, PhysicalMemoryAddress cpu_address,
|
||||
PhysicalMemoryAddress bus_address, u32& value)
|
||||
{
|
||||
SmallString str;
|
||||
str.AppendString("Invalid bus ");
|
||||
if (size == MemoryAccessSize::Byte)
|
||||
str.AppendString("byte");
|
||||
if (size == MemoryAccessSize::HalfWord)
|
||||
str.AppendString("word");
|
||||
if (size == MemoryAccessSize::Word)
|
||||
str.AppendString("dword");
|
||||
str.AppendCharacter(' ');
|
||||
if (type == MemoryAccessType::Read)
|
||||
str.AppendString("read");
|
||||
else
|
||||
str.AppendString("write");
|
||||
|
||||
str.AppendFormattedString(" at address 0x%08X (virtual address 0x%08X)", bus_address, cpu_address);
|
||||
if (type == MemoryAccessType::Write)
|
||||
str.AppendFormattedString(" (value 0x%08X)", value);
|
||||
|
||||
Log_ErrorPrint(str);
|
||||
if (type == MemoryAccessType::Read)
|
||||
value = UINT32_C(0xFFFFFFFF);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bus::ReadExpansionRegion2(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
offset &= EXP2_MASK;
|
||||
|
||||
// rx/tx buffer empty
|
||||
if (offset == 0x21)
|
||||
{
|
||||
value = 0x04 | 0x08;
|
||||
return true;
|
||||
}
|
||||
|
||||
return DoInvalidAccess(MemoryAccessType::Read, size, EXP2_BASE | offset, EXP2_BASE | offset, value);
|
||||
}
|
||||
|
||||
bool Bus::WriteExpansionRegion2(MemoryAccessSize size, u32 offset, u32 value)
|
||||
{
|
||||
offset &= EXP2_MASK;
|
||||
|
||||
if (offset == 0x23)
|
||||
{
|
||||
if (value == '\r')
|
||||
return true;
|
||||
|
||||
if (value == '\n')
|
||||
{
|
||||
if (!m_tty_line_buffer.IsEmpty())
|
||||
Log_InfoPrintf("TTY: %s", m_tty_line_buffer.GetCharArray());
|
||||
m_tty_line_buffer.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_tty_line_buffer.AppendCharacter(Truncate8(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (offset == 0x41)
|
||||
{
|
||||
Log_WarningPrintf("BIOS POST status: %02X", value & UINT32_C(0x0F));
|
||||
return true;
|
||||
}
|
||||
|
||||
return DoInvalidAccess(MemoryAccessType::Write, size, EXP2_BASE | offset, EXP2_BASE | offset, value);
|
||||
}
|
||||
|
||||
bool Bus::ReadSPU(MemoryAccessSize size, u32 offset, u32& value)
|
||||
{
|
||||
if (offset == 0x1AE)
|
||||
{
|
||||
value = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return DoInvalidAccess(MemoryAccessType::Write, size, SPU_BASE | offset, SPU_BASE | offset, value);
|
||||
}
|
||||
|
||||
bool Bus::WriteSPU(MemoryAccessSize size, u32 offset, u32 value)
|
||||
{
|
||||
return DoInvalidAccess(MemoryAccessType::Write, size, SPU_BASE | offset, SPU_BASE | offset, value);
|
||||
}
|
783
src/pse/cpu_core.cpp
Normal file
783
src/pse/cpu_core.cpp
Normal file
@ -0,0 +1,783 @@
|
||||
#include "cpu_core.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "common/state_wrapper.h"
|
||||
#include "cpu_disasm.h"
|
||||
#include <cstdio>
|
||||
Log_SetChannel(CPU::Core);
|
||||
|
||||
namespace CPU {
|
||||
bool TRACE_EXECUTION = false;
|
||||
|
||||
Core::Core() = default;
|
||||
|
||||
Core::~Core() = default;
|
||||
|
||||
bool Core::Initialize(Bus* bus)
|
||||
{
|
||||
m_bus = bus;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Core::Reset()
|
||||
{
|
||||
m_regs = {};
|
||||
m_regs.npc = RESET_VECTOR;
|
||||
FetchInstruction();
|
||||
}
|
||||
|
||||
bool Core::DoState(StateWrapper& sw)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 Core::ReadMemoryByte(VirtualMemoryAddress addr)
|
||||
{
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte, false>(addr, value);
|
||||
return Truncate8(value);
|
||||
}
|
||||
|
||||
u16 Core::ReadMemoryHalfWord(VirtualMemoryAddress addr)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 2));
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord, false>(addr, value);
|
||||
return Truncate16(value);
|
||||
}
|
||||
|
||||
u32 Core::ReadMemoryWord(VirtualMemoryAddress addr)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 4));
|
||||
u32 value;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, false>(addr, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Core::WriteMemoryByte(VirtualMemoryAddress addr, u8 value)
|
||||
{
|
||||
u32 value32 = ZeroExtend32(value);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte, false>(addr, value32);
|
||||
}
|
||||
|
||||
void Core::WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 2));
|
||||
u32 value32 = ZeroExtend32(value);
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord, false>(addr, value32);
|
||||
}
|
||||
|
||||
void Core::WriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||
{
|
||||
Assert(Common::IsAlignedPow2(addr, 4));
|
||||
DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word, false>(addr, value);
|
||||
}
|
||||
|
||||
void Core::Branch(u32 target)
|
||||
{
|
||||
m_regs.npc = target;
|
||||
m_branched = true;
|
||||
}
|
||||
|
||||
void Core::RaiseException(u32 inst_pc, Exception excode)
|
||||
{
|
||||
m_cop0_regs.EPC = m_in_branch_delay_slot ? (inst_pc - UINT32_C(4)) : inst_pc;
|
||||
m_cop0_regs.cause.Excode = excode;
|
||||
m_cop0_regs.cause.BD = m_in_branch_delay_slot;
|
||||
|
||||
// current -> previous
|
||||
m_cop0_regs.sr.mode_bits <<= 2;
|
||||
|
||||
// flush the pipeline - we don't want to execute the previously fetched instruction
|
||||
m_regs.npc = m_cop0_regs.sr.BEV ? UINT32_C(0xbfc00180) : UINT32_C(0x80000080);
|
||||
FlushPipeline();
|
||||
}
|
||||
|
||||
void Core::FlushPipeline()
|
||||
{
|
||||
// loads are flushed
|
||||
m_load_delay_reg = Reg::count;
|
||||
m_load_delay_old_value = 0;
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
m_next_load_delay_old_value = 0;
|
||||
|
||||
// not in a branch delay slot
|
||||
m_branched = false;
|
||||
m_in_branch_delay_slot = false;
|
||||
|
||||
// prefetch the next instruction
|
||||
FetchInstruction();
|
||||
}
|
||||
|
||||
u32 Core::ReadReg(Reg rs)
|
||||
{
|
||||
return rs == m_load_delay_reg ? m_load_delay_old_value : m_regs.r[static_cast<u8>(rs)];
|
||||
}
|
||||
|
||||
void Core::WriteReg(Reg rd, u32 value)
|
||||
{
|
||||
if (rd != Reg::zero)
|
||||
m_regs.r[static_cast<u8>(rd)] = value;
|
||||
}
|
||||
|
||||
void Core::WriteRegDelayed(Reg rd, u32 value)
|
||||
{
|
||||
Assert(m_next_load_delay_reg == Reg::count);
|
||||
if (rd == Reg::zero)
|
||||
return;
|
||||
|
||||
// save the old value, this will be returned if the register is read in the next instruction
|
||||
m_next_load_delay_reg = rd;
|
||||
m_next_load_delay_old_value = m_regs.r[static_cast<u8>(rd)];
|
||||
m_regs.r[static_cast<u8>(rd)] = value;
|
||||
}
|
||||
|
||||
void Core::WriteCacheControl(u32 value)
|
||||
{
|
||||
Log_WarningPrintf("Cache control <- 0x%08X", value);
|
||||
m_cache_control = value;
|
||||
}
|
||||
|
||||
static void PrintInstruction(u32 bits, u32 pc)
|
||||
{
|
||||
TinyString instr;
|
||||
DisassembleInstruction(&instr, pc, bits);
|
||||
|
||||
std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
|
||||
}
|
||||
|
||||
static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value)
|
||||
{
|
||||
return (((new_value ^ old_value) & (new_value ^ add_value)) & UINT32_C(0x80000000)) != 0;
|
||||
}
|
||||
|
||||
void Core::DisassembleAndPrint(u32 addr)
|
||||
{
|
||||
u32 bits;
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(addr, bits);
|
||||
PrintInstruction(bits, addr);
|
||||
}
|
||||
|
||||
void Core::Execute()
|
||||
{
|
||||
// now executing the instruction we previously fetched
|
||||
const Instruction inst = m_next_instruction;
|
||||
const u32 inst_pc = m_regs.pc;
|
||||
|
||||
// fetch the next instruction
|
||||
FetchInstruction();
|
||||
|
||||
// handle branch delays - we are now in a delay slot if we just branched
|
||||
m_in_branch_delay_slot = m_branched;
|
||||
m_branched = false;
|
||||
|
||||
// execute the instruction we previously fetched
|
||||
ExecuteInstruction(inst, inst_pc);
|
||||
|
||||
// next load delay
|
||||
m_load_delay_reg = m_next_load_delay_reg;
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
m_load_delay_old_value = m_next_load_delay_old_value;
|
||||
m_next_load_delay_old_value = 0;
|
||||
}
|
||||
|
||||
void Core::FetchInstruction()
|
||||
{
|
||||
DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word, true>(static_cast<VirtualMemoryAddress>(m_regs.npc),
|
||||
m_next_instruction.bits);
|
||||
m_regs.pc = m_regs.npc;
|
||||
m_regs.npc += sizeof(m_next_instruction.bits);
|
||||
}
|
||||
|
||||
void Core::ExecuteInstruction(Instruction inst, u32 inst_pc)
|
||||
{
|
||||
if (TRACE_EXECUTION)
|
||||
PrintInstruction(inst.bits, inst_pc);
|
||||
|
||||
#if 0
|
||||
if (inst_pc == 0x8005ab80)
|
||||
__debugbreak();
|
||||
#endif
|
||||
|
||||
switch (inst.op)
|
||||
{
|
||||
case InstructionOp::funct:
|
||||
{
|
||||
switch (inst.r.funct)
|
||||
{
|
||||
case InstructionFunct::sll:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rt) << inst.r.shamt;
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::srl:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rt) >> inst.r.shamt;
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::sra:
|
||||
{
|
||||
const u32 new_value = static_cast<u32>(static_cast<s32>(ReadReg(inst.r.rt)) >> inst.r.shamt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::sllv:
|
||||
{
|
||||
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
|
||||
const u32 new_value = ReadReg(inst.r.rt) << shift_amount;
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::srlv:
|
||||
{
|
||||
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
|
||||
const u32 new_value = ReadReg(inst.r.rt) >> shift_amount;
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::srav:
|
||||
{
|
||||
const u32 shift_amount = ReadReg(inst.r.rs) & UINT32_C(0x1F);
|
||||
const u32 new_value = static_cast<u32>(static_cast<s32>(ReadReg(inst.r.rt)) >> shift_amount);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::and_:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rs) & ReadReg(inst.r.rt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::or_:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rs) | ReadReg(inst.r.rt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::xor_:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rs) ^ ReadReg(inst.r.rt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::nor:
|
||||
{
|
||||
const u32 new_value = ~(ReadReg(inst.r.rs) | ReadReg(inst.r.rt));
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::add:
|
||||
{
|
||||
const u32 old_value = ReadReg(inst.r.rs);
|
||||
const u32 add_value = ReadReg(inst.r.rt);
|
||||
const u32 new_value = old_value + add_value;
|
||||
if (AddOverflow(old_value, add_value, new_value))
|
||||
RaiseException(inst_pc, Exception::Ov);
|
||||
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::addu:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rs) + ReadReg(inst.r.rt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::subu:
|
||||
{
|
||||
const u32 new_value = ReadReg(inst.r.rs) - ReadReg(inst.r.rt);
|
||||
WriteReg(inst.r.rd, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::slt:
|
||||
{
|
||||
const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.r.rs)) < static_cast<s32>(ReadReg(inst.r.rt)));
|
||||
WriteReg(inst.r.rd, result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::sltu:
|
||||
{
|
||||
const u32 result = BoolToUInt32(ReadReg(inst.r.rs) < ReadReg(inst.r.rt));
|
||||
WriteReg(inst.r.rd, result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::mfhi:
|
||||
{
|
||||
WriteReg(inst.r.rd, m_regs.hi);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::mthi:
|
||||
{
|
||||
const u32 value = ReadReg(inst.r.rs);
|
||||
m_regs.hi = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::mflo:
|
||||
{
|
||||
WriteReg(inst.r.rd, m_regs.lo);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::mtlo:
|
||||
{
|
||||
const u32 value = ReadReg(inst.r.rs);
|
||||
m_regs.lo = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::mult:
|
||||
{
|
||||
const u32 lhs = ReadReg(inst.r.rs);
|
||||
const u32 rhs = ReadReg(inst.r.rt);
|
||||
const u64 result =
|
||||
static_cast<u64>(static_cast<s64>(SignExtend64(lhs)) * static_cast<s64>(SignExtend64(rhs)));
|
||||
m_regs.hi = Truncate32(result >> 32);
|
||||
m_regs.lo = Truncate32(result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::multu:
|
||||
{
|
||||
const u32 lhs = ReadReg(inst.r.rs);
|
||||
const u32 rhs = ReadReg(inst.r.rt);
|
||||
const u64 result = ZeroExtend64(lhs) * ZeroExtend64(rhs);
|
||||
m_regs.hi = Truncate32(result >> 32);
|
||||
m_regs.lo = Truncate32(result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::div:
|
||||
{
|
||||
const s32 num = static_cast<s32>(ReadReg(inst.r.rs));
|
||||
const s32 denom = static_cast<s32>(ReadReg(inst.r.rt));
|
||||
|
||||
if (denom == 0)
|
||||
{
|
||||
// divide by zero
|
||||
m_regs.lo = (num >= 0) ? UINT32_C(0xFFFFFFFF) : UINT32_C(1);
|
||||
m_regs.hi = static_cast<u32>(num);
|
||||
}
|
||||
else if (static_cast<u32>(num) == UINT32_C(0x80000000) && denom == -1)
|
||||
{
|
||||
// unrepresentable
|
||||
m_regs.lo = UINT32_C(0x80000000);
|
||||
m_regs.hi = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_regs.lo = static_cast<u32>(num / denom);
|
||||
m_regs.hi = static_cast<u32>(num % denom);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::divu:
|
||||
{
|
||||
const u32 num = ReadReg(inst.r.rs);
|
||||
const u32 denom = ReadReg(inst.r.rt);
|
||||
|
||||
if (denom == 0)
|
||||
{
|
||||
// divide by zero
|
||||
m_regs.lo = (num >= 0) ? UINT32_C(0xFFFFFFFF) : UINT32_C(1);
|
||||
m_regs.hi = static_cast<u32>(num);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_regs.lo = num / denom;
|
||||
m_regs.hi = num % denom;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::jr:
|
||||
{
|
||||
const u32 target = ReadReg(inst.r.rs);
|
||||
Branch(target);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::jalr:
|
||||
{
|
||||
const u32 target = ReadReg(inst.r.rs);
|
||||
WriteReg(inst.r.rd, m_regs.npc);
|
||||
Branch(target);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionFunct::syscall:
|
||||
{
|
||||
RaiseException(inst_pc, Exception::Syscall);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lui:
|
||||
{
|
||||
WriteReg(inst.i.rt, inst.i.imm_zext32() << 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::andi:
|
||||
{
|
||||
WriteReg(inst.i.rt, ReadReg(inst.i.rs) & inst.i.imm_zext32());
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::ori:
|
||||
{
|
||||
WriteReg(inst.i.rt, ReadReg(inst.i.rs) | inst.i.imm_zext32());
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::addi:
|
||||
{
|
||||
const u32 old_value = ReadReg(inst.i.rs);
|
||||
const u32 add_value = inst.i.imm_sext32();
|
||||
const u32 new_value = old_value + add_value;
|
||||
if (AddOverflow(old_value, add_value, new_value))
|
||||
RaiseException(inst_pc, Exception::Ov);
|
||||
|
||||
WriteReg(inst.i.rt, new_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::addiu:
|
||||
{
|
||||
WriteReg(inst.i.rt, ReadReg(inst.i.rs) + inst.i.imm_sext32());
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::slti:
|
||||
{
|
||||
const u32 result = BoolToUInt32(static_cast<s32>(ReadReg(inst.i.rs)) < static_cast<s32>(inst.i.imm_sext32()));
|
||||
WriteReg(inst.i.rt, result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::sltiu:
|
||||
{
|
||||
const u32 result = BoolToUInt32(ReadReg(inst.i.rs) < inst.i.imm_sext32());
|
||||
WriteReg(inst.i.rt, result);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lb:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u8 value = ReadMemoryByte(addr);
|
||||
WriteRegDelayed(inst.i.rt, SignExtend32(value));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lh:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u16 value = ReadMemoryHalfWord(addr);
|
||||
WriteRegDelayed(inst.i.rt, SignExtend32(value));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lw:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u32 value = ReadMemoryWord(addr);
|
||||
WriteRegDelayed(inst.i.rt, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lbu:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u8 value = ReadMemoryByte(addr);
|
||||
WriteRegDelayed(inst.i.rt, ZeroExtend32(value));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::lhu:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u16 value = ReadMemoryHalfWord(addr);
|
||||
WriteRegDelayed(inst.i.rt, ZeroExtend32(value));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::sb:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u8 value = Truncate8(ReadReg(inst.i.rt));
|
||||
WriteMemoryByte(addr, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::sh:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u16 value = Truncate16(ReadReg(inst.i.rt));
|
||||
WriteMemoryHalfWord(addr, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::sw:
|
||||
{
|
||||
const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32();
|
||||
const u32 value = ReadReg(inst.i.rt);
|
||||
WriteMemoryWord(addr, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::j:
|
||||
{
|
||||
Branch((m_regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::jal:
|
||||
{
|
||||
m_regs.ra = m_regs.npc;
|
||||
Branch((m_regs.pc & UINT32_C(0xF0000000)) | (inst.j.target << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::beq:
|
||||
{
|
||||
const bool branch = (ReadReg(inst.i.rs) == ReadReg(inst.i.rt));
|
||||
if (branch)
|
||||
Branch(m_regs.pc + (inst.i.imm_sext32() << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::bne:
|
||||
{
|
||||
const bool branch = (ReadReg(inst.i.rs) != ReadReg(inst.i.rt));
|
||||
if (branch)
|
||||
Branch(m_regs.pc + (inst.i.imm_sext32() << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::bgtz:
|
||||
{
|
||||
const bool branch = (static_cast<s32>(ReadReg(inst.i.rs)) > 0);
|
||||
if (branch)
|
||||
Branch(m_regs.pc + (inst.i.imm_sext32() << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::blez:
|
||||
{
|
||||
const bool branch = (static_cast<s32>(ReadReg(inst.i.rs)) <= 0);
|
||||
if (branch)
|
||||
Branch(m_regs.pc + (inst.i.imm_sext32() << 2));
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::b:
|
||||
{
|
||||
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
|
||||
|
||||
// bgez is the inverse of bltz, so simply do ltz and xor the result
|
||||
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
|
||||
const bool branch = (static_cast<s32>(ReadReg(inst.i.rs)) < 0) ^ bgez;
|
||||
if (branch)
|
||||
{
|
||||
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
|
||||
if (link)
|
||||
m_regs.ra = m_regs.npc;
|
||||
|
||||
Branch(m_regs.pc + (inst.i.imm_sext32() << 2));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::cop0:
|
||||
{
|
||||
if (!m_cop0_regs.sr.CU0 && InUserMode())
|
||||
{
|
||||
Log_WarningPrintf("Coprocessor 0 not present in user mode");
|
||||
RaiseException(inst_pc, Exception::CpU);
|
||||
return;
|
||||
}
|
||||
|
||||
ExecuteCop0Instruction(inst, inst_pc);
|
||||
}
|
||||
break;
|
||||
|
||||
// COP1/COP3 are not present
|
||||
case InstructionOp::cop1:
|
||||
case InstructionOp::cop2:
|
||||
{
|
||||
RaiseException(inst_pc, Exception::CpU);
|
||||
}
|
||||
break;
|
||||
|
||||
case InstructionOp::cop3:
|
||||
{
|
||||
Panic("GTE not implemented");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::ExecuteCop0Instruction(Instruction inst, u32 inst_pc)
|
||||
{
|
||||
switch (inst.cop.cop0_op())
|
||||
{
|
||||
case Cop0Instruction::mtc0:
|
||||
{
|
||||
const u32 value = ReadReg(inst.r.rt);
|
||||
switch (static_cast<Cop0Reg>(inst.r.rd.GetValue()))
|
||||
{
|
||||
case Cop0Reg::BPC:
|
||||
{
|
||||
m_cop0_regs.BPC = value;
|
||||
Log_WarningPrintf("COP0 BPC <- %08X", value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::BPCM:
|
||||
{
|
||||
m_cop0_regs.BPCM = value;
|
||||
Log_WarningPrintf("COP0 BPCM <- %08X", value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::BDA:
|
||||
{
|
||||
m_cop0_regs.BDA = value;
|
||||
Log_WarningPrintf("COP0 BDA <- %08X", value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::BDAM:
|
||||
{
|
||||
m_cop0_regs.BDAM = value;
|
||||
Log_WarningPrintf("COP0 BDAM <- %08X", value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::JUMPDEST:
|
||||
{
|
||||
Log_WarningPrintf("Ignoring write to Cop0 JUMPDEST");
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::DCIC:
|
||||
{
|
||||
m_cop0_regs.dcic.bits =
|
||||
(m_cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK);
|
||||
Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, m_cop0_regs.dcic.bits);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::SR:
|
||||
{
|
||||
m_cop0_regs.sr.bits =
|
||||
(m_cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK);
|
||||
Log_WarningPrintf("COP0 SR <- %08X (now %08X)", value, m_cop0_regs.sr.bits);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Reg::CAUSE:
|
||||
{
|
||||
m_cop0_regs.cause.bits =
|
||||
(m_cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | (value & Cop0Registers::CAUSE::WRITE_MASK);
|
||||
Log_WarningPrintf("COP0 CAUSE <- %08X (now %08X)", value, m_cop0_regs.cause.bits);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Panic("Unknown COP0 reg");
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Instruction::mfc0:
|
||||
{
|
||||
u32 value;
|
||||
switch (static_cast<Cop0Reg>(inst.r.rd.GetValue()))
|
||||
{
|
||||
case Cop0Reg::BPC:
|
||||
value = m_cop0_regs.BPC;
|
||||
break;
|
||||
|
||||
case Cop0Reg::BPCM:
|
||||
value = m_cop0_regs.BPCM;
|
||||
break;
|
||||
|
||||
case Cop0Reg::BDA:
|
||||
value = m_cop0_regs.BDA;
|
||||
break;
|
||||
|
||||
case Cop0Reg::BDAM:
|
||||
value = m_cop0_regs.BDAM;
|
||||
break;
|
||||
|
||||
case Cop0Reg::DCIC:
|
||||
value = m_cop0_regs.dcic.bits;
|
||||
break;
|
||||
|
||||
case Cop0Reg::SR:
|
||||
value = m_cop0_regs.sr.bits;
|
||||
break;
|
||||
|
||||
case Cop0Reg::CAUSE:
|
||||
value = m_cop0_regs.cause.bits;
|
||||
break;
|
||||
|
||||
case Cop0Reg::EPC:
|
||||
value = m_cop0_regs.EPC;
|
||||
break;
|
||||
|
||||
default:
|
||||
Panic("Unknown COP0 reg");
|
||||
value = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
WriteRegDelayed(inst.r.rt, value);
|
||||
}
|
||||
break;
|
||||
|
||||
case Cop0Instruction::rfe:
|
||||
{
|
||||
// restore mode
|
||||
m_cop0_regs.sr.mode_bits = (m_cop0_regs.sr.mode_bits & UINT32_C(0b110000)) | (m_cop0_regs.sr.mode_bits >> 2);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Panic("Unhandled instruction");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CPU
|
84
src/pse/cpu_core.h
Normal file
84
src/pse/cpu_core.h
Normal file
@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "cpu_types.h"
|
||||
#include "types.h"
|
||||
|
||||
class StateWrapper;
|
||||
|
||||
class Bus;
|
||||
|
||||
namespace CPU {
|
||||
|
||||
class Core
|
||||
{
|
||||
public:
|
||||
static constexpr VirtualMemoryAddress RESET_VECTOR = 0xbfc00000;
|
||||
|
||||
Core();
|
||||
~Core();
|
||||
|
||||
bool Initialize(Bus* bus);
|
||||
void Reset();
|
||||
bool DoState(StateWrapper& sw);
|
||||
|
||||
void Execute();
|
||||
|
||||
private:
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch>
|
||||
void DoMemoryAccess(VirtualMemoryAddress address, u32& value);
|
||||
|
||||
u8 ReadMemoryByte(VirtualMemoryAddress addr);
|
||||
u16 ReadMemoryHalfWord(VirtualMemoryAddress addr);
|
||||
u32 ReadMemoryWord(VirtualMemoryAddress addr);
|
||||
void WriteMemoryByte(VirtualMemoryAddress addr, u8 value);
|
||||
void WriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value);
|
||||
void WriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
||||
|
||||
// state helpers
|
||||
bool InUserMode() const { return m_cop0_regs.sr.KUc; }
|
||||
bool InKernelMode() const { return !m_cop0_regs.sr.KUc; }
|
||||
|
||||
void DisassembleAndPrint(u32 addr);
|
||||
|
||||
// Fetches the instruction at m_regs.npc
|
||||
void FetchInstruction();
|
||||
void ExecuteInstruction(Instruction inst, u32 inst_pc);
|
||||
void ExecuteCop0Instruction(Instruction inst, u32 inst_pc);
|
||||
void Branch(u32 target);
|
||||
void RaiseException(u32 inst_pc, Exception excode);
|
||||
|
||||
// clears pipeline of load/branch delays
|
||||
void FlushPipeline();
|
||||
|
||||
// helper functions for registers which aren't writable
|
||||
u32 ReadReg(Reg rs);
|
||||
void WriteReg(Reg rd, u32 value);
|
||||
|
||||
// helper for generating a load delay write
|
||||
void WriteRegDelayed(Reg rd, u32 value);
|
||||
|
||||
// write to cache control register
|
||||
void WriteCacheControl(u32 value);
|
||||
|
||||
Bus* m_bus = nullptr;
|
||||
Registers m_regs = {};
|
||||
Instruction m_next_instruction = {};
|
||||
bool m_in_branch_delay_slot = false;
|
||||
bool m_branched = false;
|
||||
|
||||
// load delays
|
||||
Reg m_load_delay_reg = Reg::count;
|
||||
u32 m_load_delay_old_value = 0;
|
||||
Reg m_next_load_delay_reg = Reg::count;
|
||||
u32 m_next_load_delay_old_value = 0;
|
||||
|
||||
u32 m_cache_control = 0;
|
||||
|
||||
Cop0Registers m_cop0_regs = {};
|
||||
};
|
||||
|
||||
extern bool TRACE_EXECUTION;
|
||||
|
||||
} // namespace CPU
|
||||
|
||||
#include "cpu_core.inl"
|
78
src/pse/cpu_core.inl
Normal file
78
src/pse/cpu_core.inl
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include "YBaseLib/Assert.h"
|
||||
#include "bus.h"
|
||||
#include "cpu_core.h"
|
||||
|
||||
namespace CPU {
|
||||
|
||||
template<MemoryAccessType type, MemoryAccessSize size, bool is_instruction_fetch>
|
||||
void Core::DoMemoryAccess(VirtualMemoryAddress address, u32& value)
|
||||
{
|
||||
switch (address >> 29)
|
||||
{
|
||||
case 0x00: // KUSEG 0M-512M
|
||||
{
|
||||
if constexpr (type == MemoryAccessType::Write)
|
||||
{
|
||||
if (m_cop0_regs.sr.Isc)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
Panic("Bus error");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x01: // KUSEG 512M-1024M
|
||||
case 0x02: // KUSEG 1024M-1536M
|
||||
case 0x03: // KUSEG 1536M-2048M
|
||||
{
|
||||
// Above 512mb raises an exception.
|
||||
Panic("Bad user access");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x04: // KSEG0 - physical memory cached
|
||||
{
|
||||
if constexpr (type == MemoryAccessType::Write)
|
||||
{
|
||||
if (m_cop0_regs.sr.Isc)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
Panic("Bus error");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x05: // KSEG1 - physical memory uncached
|
||||
{
|
||||
if (!m_bus->DispatchAccess<type, size>(address, address & UINT32_C(0x1FFFFFFF), value))
|
||||
Panic("Bus error");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x06: // KSEG2
|
||||
case 0x07: // KSEG2
|
||||
{
|
||||
if (address == 0xFFFE0130)
|
||||
{
|
||||
if constexpr (type == MemoryAccessType::Read)
|
||||
value = m_cache_control;
|
||||
else
|
||||
WriteCacheControl(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Panic("KSEG2 access");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CPU
|
310
src/pse/cpu_disasm.cpp
Normal file
310
src/pse/cpu_disasm.cpp
Normal file
@ -0,0 +1,310 @@
|
||||
#include "cpu_disasm.h"
|
||||
#include <array>
|
||||
|
||||
namespace CPU {
|
||||
|
||||
enum Operand : u8
|
||||
{
|
||||
Operand_None,
|
||||
i_rs,
|
||||
i_rt,
|
||||
i_imm,
|
||||
j_target,
|
||||
r_rs,
|
||||
r_rt,
|
||||
r_rd,
|
||||
r_shamt,
|
||||
r_funct
|
||||
};
|
||||
|
||||
struct TableEntry
|
||||
{
|
||||
const char* format;
|
||||
};
|
||||
|
||||
static const std::array<const char*, 32> s_reg_names = {
|
||||
{"$zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
|
||||
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"}};
|
||||
|
||||
static const std::array<const char*, 64> s_base_table = {{
|
||||
"", // 0
|
||||
"UNKNOWN", // 1
|
||||
"j $jt", // 2
|
||||
"jal $jt", // 3
|
||||
"beq $rs, $rt, $rel", // 4
|
||||
"bne $rs, $rt, $rel", // 5
|
||||
"blez $rs, $rel", // 6
|
||||
"bgtz $rs, $rel", // 7
|
||||
"addi $rt, $rs, $imm", // 8
|
||||
"addiu $rt, $rs, $imm", // 9
|
||||
"slti $rt, $rs, $imm", // 10
|
||||
"sltiu $rt, $rs, $immu", // 11
|
||||
"andi $rt, $rs, $immu", // 12
|
||||
"ori $rt, $rs, $immu", // 13
|
||||
"UNKNOWN", // 14
|
||||
"lui $rt, $imm", // 15
|
||||
"UNKNOWN", // 16
|
||||
"UNKNOWN", // 17
|
||||
"UNKNOWN", // 18
|
||||
"UNKNOWN", // 19
|
||||
"UNKNOWN", // 20
|
||||
"UNKNOWN", // 21
|
||||
"UNKNOWN", // 22
|
||||
"UNKNOWN", // 23
|
||||
"UNKNOWN", // 24
|
||||
"UNKNOWN", // 25
|
||||
"UNKNOWN", // 26
|
||||
"UNKNOWN", // 27
|
||||
"UNKNOWN", // 28
|
||||
"UNKNOWN", // 29
|
||||
"UNKNOWN", // 30
|
||||
"UNKNOWN", // 31
|
||||
"lb $rt, $offsetrs", // 32
|
||||
"lh $rt, $offsetrs", // 33
|
||||
"UNKNOWN", // 34
|
||||
"lw $rt, $offsetrs", // 35
|
||||
"lbu $rt, $offsetrs", // 36
|
||||
"lhu $rt, $offsetrs", // 37
|
||||
"UNKNOWN", // 38
|
||||
"UNKNOWN", // 39
|
||||
"sb $rt, $offsetrs", // 40
|
||||
"sh $rt, $offsetrs", // 41
|
||||
"UNKNOWN", // 42
|
||||
"sw $rt, $offsetrs", // 43
|
||||
"UNKNOWN", // 44
|
||||
"UNKNOWN", // 45
|
||||
"UNKNOWN", // 46
|
||||
"UNKNOWN", // 47
|
||||
"UNKNOWN", // 48
|
||||
"UNKNOWN", // 49
|
||||
"UNKNOWN", // 50
|
||||
"UNKNOWN", // 51
|
||||
"UNKNOWN", // 52
|
||||
"UNKNOWN", // 53
|
||||
"UNKNOWN", // 54
|
||||
"UNKNOWN", // 55
|
||||
"UNKNOWN", // 56
|
||||
"UNKNOWN", // 57
|
||||
"UNKNOWN", // 58
|
||||
"UNKNOWN", // 59
|
||||
"UNKNOWN", // 60
|
||||
"UNKNOWN", // 61
|
||||
"UNKNOWN", // 62
|
||||
"UNKNOWN" // 63
|
||||
}};
|
||||
|
||||
static const std::array<const char*, 64> s_special_table = {{
|
||||
"sll $rd, $rt, $shamt", // 0
|
||||
"UNKNOWN", // 1
|
||||
"srl $rd, $rt, $shamt", // 2
|
||||
"sra $rd, $rt, $shamt", // 3
|
||||
"sllv $rd, $rt, $rs", // 4
|
||||
"UNKNOWN", // 5
|
||||
"srlv $rd, $rt, $rs", // 6
|
||||
"srav $rd, $rt, $rs", // 7
|
||||
"jr $rs", // 8
|
||||
"jalr $rd, $rs", // 9
|
||||
"UNKNOWN", // 10
|
||||
"UNKNOWN", // 11
|
||||
"syscall", // 12
|
||||
"UNKNOWN", // 13
|
||||
"UNKNOWN", // 14
|
||||
"UNKNOWN", // 15
|
||||
"mfhi $rd", // 16
|
||||
"mthi $rs", // 17
|
||||
"mflo $rd", // 18
|
||||
"mtlo $rs", // 19
|
||||
"UNKNOWN", // 20
|
||||
"UNKNOWN", // 21
|
||||
"UNKNOWN", // 22
|
||||
"UNKNOWN", // 23
|
||||
"mult $rs, $rt", // 24
|
||||
"multu $rs, $rt", // 25
|
||||
"div $rs, $rt", // 26
|
||||
"divu $rs, $rt", // 27
|
||||
"UNKNOWN", // 28
|
||||
"UNKNOWN", // 29
|
||||
"UNKNOWN", // 30
|
||||
"UNKNOWN", // 31
|
||||
"add $rd, $rs, $rt", // 32
|
||||
"addu $rd, $rs, $rt", // 33
|
||||
"sub $rd, $rs, $rt", // 34
|
||||
"subu $rd, $rs, $rt", // 35
|
||||
"and $rd, $rs, $rt", // 36
|
||||
"or $rd, $rs, $rt", // 37
|
||||
"xor $rd, $rs, $rt", // 38
|
||||
"nor $rd, $rs, $rt", // 39
|
||||
"UNKNOWN", // 40
|
||||
"UNKNOWN", // 41
|
||||
"slt $rd, $rs, $rt", // 42
|
||||
"sltu $rd, $rs, $rt", // 43
|
||||
"UNKNOWN", // 44
|
||||
"UNKNOWN", // 45
|
||||
"UNKNOWN", // 46
|
||||
"UNKNOWN", // 47
|
||||
"UNKNOWN", // 48
|
||||
"UNKNOWN", // 49
|
||||
"UNKNOWN", // 50
|
||||
"UNKNOWN", // 51
|
||||
"UNKNOWN", // 52
|
||||
"UNKNOWN", // 53
|
||||
"UNKNOWN", // 54
|
||||
"UNKNOWN", // 55
|
||||
"UNKNOWN", // 56
|
||||
"UNKNOWN", // 57
|
||||
"UNKNOWN", // 58
|
||||
"UNKNOWN", // 59
|
||||
"UNKNOWN", // 60
|
||||
"UNKNOWN", // 61
|
||||
"UNKNOWN", // 62
|
||||
"UNKNOWN" // 63
|
||||
}};
|
||||
|
||||
static const std::array<std::pair<Cop0Instruction, const char*>, 6> s_cop0_table = {
|
||||
{{Cop0Instruction::mfc0, "mfc0 $rt, $coprd"},
|
||||
{Cop0Instruction::cfc0, "cfc0 $rt, $copcr"},
|
||||
{Cop0Instruction::mtc0, "mtc0 $rt, $coprd"},
|
||||
{Cop0Instruction::ctc0, "ctc0 $rt, $copcr"},
|
||||
{Cop0Instruction::bc0c, "bc0$copcc $rel"},
|
||||
{Cop0Instruction::rfe, "rfe"}}};
|
||||
|
||||
static void FormatInstruction(String* dest, const Instruction inst, u32 pc, const char* format)
|
||||
{
|
||||
dest->Clear();
|
||||
|
||||
const char* str = format;
|
||||
while (*str != '\0')
|
||||
{
|
||||
const char ch = *(str++);
|
||||
if (ch != '$')
|
||||
{
|
||||
dest->AppendCharacter(ch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::strncmp(str, "rs", 2) == 0)
|
||||
{
|
||||
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rs.GetValue())]);
|
||||
str += 2;
|
||||
}
|
||||
else if (std::strncmp(str, "rt", 2) == 0)
|
||||
{
|
||||
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rt.GetValue())]);
|
||||
str += 2;
|
||||
}
|
||||
else if (std::strncmp(str, "rd", 2) == 0)
|
||||
{
|
||||
dest->AppendString(s_reg_names[static_cast<u8>(inst.r.rd.GetValue())]);
|
||||
str += 2;
|
||||
}
|
||||
else if (std::strncmp(str, "shamt", 5) == 0)
|
||||
{
|
||||
dest->AppendFormattedString("%d", ZeroExtend32(inst.r.shamt.GetValue()));
|
||||
str += 5;
|
||||
}
|
||||
else if (std::strncmp(str, "immu", 4) == 0)
|
||||
{
|
||||
dest->AppendFormattedString("%u", inst.i.imm_zext32());
|
||||
str += 4;
|
||||
}
|
||||
else if (std::strncmp(str, "imm", 3) == 0)
|
||||
{
|
||||
// dest->AppendFormattedString("%d", static_cast<int>(inst.i.imm_sext32()));
|
||||
dest->AppendFormattedString("%04x", inst.i.imm_zext32());
|
||||
str += 3;
|
||||
}
|
||||
else if (std::strncmp(str, "rel", 3) == 0)
|
||||
{
|
||||
const u32 target = (pc + UINT32_C(4)) + (inst.i.imm_sext32() << 2);
|
||||
dest->AppendFormattedString("%08x", target);
|
||||
str += 3;
|
||||
}
|
||||
else if (std::strncmp(str, "offsetrs", 8) == 0)
|
||||
{
|
||||
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
|
||||
dest->AppendFormattedString("%d(%s)", offset, s_reg_names[static_cast<u8>(inst.i.rs.GetValue())]);
|
||||
str += 8;
|
||||
}
|
||||
else if (std::strncmp(str, "jt", 2) == 0)
|
||||
{
|
||||
const u32 target = ((pc + UINT32_C(4)) & UINT32_C(0xF0000000)) | (inst.j.target << 2);
|
||||
dest->AppendFormattedString("%08x", target);
|
||||
str += 2;
|
||||
}
|
||||
else if (std::strncmp(str, "copcc", 5) == 0)
|
||||
{
|
||||
dest->AppendCharacter(((inst.bits & (UINT32_C(1) << 24)) != 0) ? 't' : 'f');
|
||||
str += 5;
|
||||
}
|
||||
else if (std::strncmp(str, "coprd", 5) == 0 || std::strncmp(str, "copcr", 5) == 0)
|
||||
{
|
||||
dest->AppendFormattedString("%u", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
|
||||
str += 5;
|
||||
}
|
||||
else if (std::strncmp(str, "cop", 3) == 0)
|
||||
{
|
||||
dest->AppendFormattedString("%u", static_cast<u8>(inst.op.GetValue()) & INSTRUCTION_COP_N_MASK);
|
||||
str += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
Panic("Unknown operand");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void FormatCopInstruction(String* dest, u32 pc, const Instruction inst, const std::pair<T, const char*>* table,
|
||||
size_t table_size, T table_key)
|
||||
{
|
||||
for (size_t i = 0; i < table_size; i++)
|
||||
{
|
||||
if (table[i].first == table_key)
|
||||
{
|
||||
FormatInstruction(dest, inst, pc, table[i].second);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dest->Format("<cop%u 0x%07X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
}
|
||||
|
||||
void DisassembleInstruction(String* dest, u32 pc, u32 bits)
|
||||
{
|
||||
const Instruction inst{bits};
|
||||
switch (inst.op)
|
||||
{
|
||||
case InstructionOp::funct:
|
||||
FormatInstruction(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
|
||||
return;
|
||||
|
||||
case InstructionOp::cop0:
|
||||
FormatCopInstruction(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.cop0_op());
|
||||
return;
|
||||
|
||||
case InstructionOp::cop1:
|
||||
case InstructionOp::cop2:
|
||||
case InstructionOp::cop3:
|
||||
dest->Format("<cop%u 0x%07X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
|
||||
break;
|
||||
|
||||
// special case for bltz/bgez{al}
|
||||
case InstructionOp::b:
|
||||
{
|
||||
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
|
||||
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
|
||||
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
|
||||
if (link)
|
||||
FormatInstruction(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
|
||||
else
|
||||
FormatInstruction(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
FormatInstruction(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CPU
|
7
src/pse/cpu_disasm.h
Normal file
7
src/pse/cpu_disasm.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "YBaseLib/String.h"
|
||||
#include "cpu_types.h"
|
||||
|
||||
namespace CPU {
|
||||
void DisassembleInstruction(String* dest, u32 pc, u32 bits);
|
||||
} // namespace CPU
|
330
src/pse/cpu_types.h
Normal file
330
src/pse/cpu_types.h
Normal file
@ -0,0 +1,330 @@
|
||||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace CPU {
|
||||
|
||||
enum class Reg : u8
|
||||
{
|
||||
zero,
|
||||
at,
|
||||
v0,
|
||||
v1,
|
||||
a0,
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
t0,
|
||||
t1,
|
||||
t2,
|
||||
t3,
|
||||
t4,
|
||||
t5,
|
||||
t6,
|
||||
t7,
|
||||
s0,
|
||||
s1,
|
||||
s2,
|
||||
s3,
|
||||
s4,
|
||||
s5,
|
||||
s6,
|
||||
s7,
|
||||
t8,
|
||||
t9,
|
||||
k0,
|
||||
k1,
|
||||
gp,
|
||||
sp,
|
||||
fp,
|
||||
ra,
|
||||
count
|
||||
};
|
||||
|
||||
enum class InstructionOp : u8
|
||||
{
|
||||
funct = 0,
|
||||
b = 1, // i.rt 0 - bltz, 1 - bgez, 16 - bltzal, 17 - bgezal
|
||||
j = 2,
|
||||
jal = 3,
|
||||
beq = 4,
|
||||
bne = 5,
|
||||
blez = 6,
|
||||
bgtz = 7,
|
||||
addi = 8,
|
||||
addiu = 9,
|
||||
slti = 10,
|
||||
sltiu = 11,
|
||||
andi = 12,
|
||||
ori = 13,
|
||||
xori = 14,
|
||||
lui = 15,
|
||||
cop0 = 16,
|
||||
cop1 = 17,
|
||||
cop2 = 18,
|
||||
cop3 = 19,
|
||||
lb = 32,
|
||||
lh = 33,
|
||||
lwl = 34,
|
||||
lw = 35,
|
||||
lbu = 36,
|
||||
lhu = 37,
|
||||
lwr = 38,
|
||||
sb = 40,
|
||||
sh = 41,
|
||||
swl = 42,
|
||||
sw = 43,
|
||||
swr = 46
|
||||
};
|
||||
constexpr u8 INSTRUCTION_COP_BITS = 0x10;
|
||||
constexpr u8 INSTRUCTION_COP_MASK = 0x3C;
|
||||
constexpr u8 INSTRUCTION_COP_N_MASK = 0x03;
|
||||
|
||||
enum class InstructionFunct : u8
|
||||
{
|
||||
sll = 0,
|
||||
srl = 2,
|
||||
sra = 3,
|
||||
sllv = 4,
|
||||
srlv = 6,
|
||||
srav = 7,
|
||||
jr = 8,
|
||||
jalr = 9,
|
||||
syscall = 12,
|
||||
break_ = 13,
|
||||
mfhi = 16,
|
||||
mthi = 17,
|
||||
mflo = 18,
|
||||
mtlo = 19,
|
||||
mult = 24,
|
||||
multu = 25,
|
||||
div = 26,
|
||||
divu = 27,
|
||||
add = 32,
|
||||
addu = 33,
|
||||
sub = 34,
|
||||
subu = 35,
|
||||
and_ = 36,
|
||||
or_ = 37,
|
||||
xor_ = 38,
|
||||
nor = 39,
|
||||
sh = 41,
|
||||
slt = 42,
|
||||
sltu = 43
|
||||
};
|
||||
|
||||
enum class Cop0Instruction : u32 // 25:21 | 0:5
|
||||
{
|
||||
mfc0 = 0b00000'000000,
|
||||
cfc0 = 0b00010'000000,
|
||||
mtc0 = 0b00100'000000,
|
||||
ctc0 = 0b00110'000000,
|
||||
bc0c = 0b01000'000000,
|
||||
tlbr = 0b10000'000001,
|
||||
tlbwi = 0b10000'000010,
|
||||
tlbwr = 0b10000'000100,
|
||||
tlbp = 0b10000'001000,
|
||||
rfe = 0b10000'010000,
|
||||
};
|
||||
|
||||
union Instruction
|
||||
{
|
||||
u32 bits;
|
||||
|
||||
BitField<u32, InstructionOp, 26, 6> op; // function/instruction
|
||||
|
||||
union
|
||||
{
|
||||
BitField<u32, Reg, 21, 5> rs;
|
||||
BitField<u32, Reg, 16, 5> rt;
|
||||
BitField<u32, u16, 0, 16> imm;
|
||||
|
||||
u32 imm_sext32() const { return SignExtend32(imm.GetValue()); }
|
||||
u32 imm_zext32() const { return ZeroExtend32(imm.GetValue()); }
|
||||
} i;
|
||||
|
||||
union
|
||||
{
|
||||
BitField<u32, u32, 0, 26> target;
|
||||
} j;
|
||||
|
||||
union
|
||||
{
|
||||
BitField<u32, Reg, 21, 5> rs;
|
||||
BitField<u32, Reg, 16, 5> rt;
|
||||
BitField<u32, Reg, 11, 5> rd;
|
||||
BitField<u32, u8, 6, 5> shamt;
|
||||
BitField<u32, InstructionFunct, 0, 6> funct;
|
||||
} r;
|
||||
|
||||
union
|
||||
{
|
||||
u32 bits;
|
||||
BitField<u32, u8, 26, 2> cop_n;
|
||||
BitField<u32, u16, 0, 16> imm16;
|
||||
BitField<u32, u32, 0, 25> imm25;
|
||||
|
||||
Cop0Instruction cop0_op() const
|
||||
{
|
||||
return static_cast<Cop0Instruction>(((bits >> 15) & UINT32_C(0b11111000000)) | (bits & UINT32_C(0b111111)));
|
||||
}
|
||||
} cop;
|
||||
};
|
||||
|
||||
struct Registers
|
||||
{
|
||||
union
|
||||
{
|
||||
u32 r[32];
|
||||
|
||||
struct
|
||||
{
|
||||
u32 zero;
|
||||
u32 at;
|
||||
u32 v0;
|
||||
u32 v1;
|
||||
u32 a0;
|
||||
u32 a1;
|
||||
u32 a2;
|
||||
u32 a3;
|
||||
u32 t0;
|
||||
u32 t1;
|
||||
u32 t2;
|
||||
u32 t3;
|
||||
u32 t4;
|
||||
u32 t5;
|
||||
u32 t6;
|
||||
u32 t7;
|
||||
u32 s0;
|
||||
u32 s1;
|
||||
u32 s2;
|
||||
u32 s3;
|
||||
u32 s4;
|
||||
u32 s5;
|
||||
u32 s6;
|
||||
u32 s7;
|
||||
u32 t8;
|
||||
u32 t9;
|
||||
u32 k0;
|
||||
u32 k1;
|
||||
u32 gp;
|
||||
u32 sp;
|
||||
u32 fp;
|
||||
u32 ra;
|
||||
};
|
||||
};
|
||||
|
||||
u32 pc;
|
||||
u32 hi;
|
||||
u32 lo;
|
||||
u32 npc;
|
||||
};
|
||||
|
||||
enum class Cop0Reg : u8
|
||||
{
|
||||
BPC = 3,
|
||||
BDA = 5,
|
||||
JUMPDEST = 6,
|
||||
DCIC = 7,
|
||||
BadVaddr = 8,
|
||||
BDAM = 9,
|
||||
BPCM = 11,
|
||||
SR = 12,
|
||||
CAUSE = 13,
|
||||
EPC = 14,
|
||||
PRID = 15
|
||||
};
|
||||
|
||||
enum class Exception : u8
|
||||
{
|
||||
INT = 0x00, // interrupt
|
||||
MOD = 0x01, // tlb modification
|
||||
TLBL = 0x02, // tlb load
|
||||
TLBS = 0x03, // tlb store
|
||||
AdEL = 0x04, // address error, data load/instruction fetch
|
||||
AdES = 0x05, // address error, data store
|
||||
IBE = 0x06, // bus error on instruction fetch
|
||||
DBE = 0x07, // bus error on data load/store
|
||||
Syscall = 0x08, // system call instruction
|
||||
BP = 0x09, // break instruction
|
||||
RI = 0x0A, // reserved instruction
|
||||
CpU = 0x0B, // coprocessor unusable
|
||||
Ov = 0x0C, // arithmetic overflow
|
||||
};
|
||||
|
||||
struct Cop0Registers
|
||||
{
|
||||
u32 BPC; // breakpoint on execute
|
||||
u32 BDA; // breakpoint on data access
|
||||
u32 JUMPDEST; // randomly memorized jump address
|
||||
u32 BadVaddr; // bad virtual address value
|
||||
u32 BDAM; // data breakpoint mask
|
||||
u32 BPCM; // execute breakpoint mask
|
||||
u32 EPC; // return address from trap
|
||||
u32 PRID; // processor ID
|
||||
|
||||
union SR
|
||||
{
|
||||
u32 bits;
|
||||
BitField<u32, bool, 0, 1> IEc; // current interrupt enable
|
||||
BitField<u32, bool, 1, 1> KUc; // current kernel/user mode, kernel = 1
|
||||
BitField<u32, bool, 2, 1> IEp; // previous interrupt enable
|
||||
BitField<u32, bool, 3, 1> KUp; // previous kernel/user mode, kernel = 1
|
||||
BitField<u32, bool, 4, 1> IEo; // old interrupt enable
|
||||
BitField<u32, bool, 5, 1> KUo; // old kernel/user mode, kernel = 1
|
||||
BitField<u32, u8, 8, 8> Im; // interrupt mask, set to 1 = allowed to trigger
|
||||
BitField<u32, bool, 16, 1> Isc; // isolate cache, no writes to memory occur
|
||||
BitField<u32, bool, 17, 1> Swc; // swap data and instruction caches
|
||||
BitField<u32, bool, 18, 1> PZ; // zero cache parity bits
|
||||
BitField<u32, bool, 19, 1> CM; // last isolated load contains data from memory (tag matches?)
|
||||
BitField<u32, bool, 20, 1> PE; // cache parity error
|
||||
BitField<u32, bool, 21, 1> TS; // tlb shutdown - matched two entries
|
||||
BitField<u32, bool, 22, 1> BEV; // boot exception vectors, 0 = KSEG0, 1 = KSEG1
|
||||
BitField<u32, bool, 25, 1> RE; // reverse endianness in user mode
|
||||
BitField<u32, bool, 28, 1> CU0; // coprocessor 0 enable in user mode
|
||||
BitField<u32, bool, 29, 1> CU1; // coprocessor 1 enable in user mode
|
||||
BitField<u32, bool, 30, 1> CU2; // coprocessor 2 enable in user mode
|
||||
BitField<u32, bool, 31, 1> CU3; // coprocessor 3 enable in user mode
|
||||
|
||||
BitField<u32, u8, 0, 6> mode_bits;
|
||||
BitField<u32, u8, 28, 2> coprocessor_enable_mask;
|
||||
|
||||
static constexpr u32 WRITE_MASK = 0b1111'0010'0111'1111'1111'0011'0011'1111;
|
||||
} sr;
|
||||
|
||||
union CAUSE
|
||||
{
|
||||
u32 bits;
|
||||
BitField<u32, Exception, 2, 5> Excode; // which exception occurred
|
||||
BitField<u32, u8, 8, 8> Ip; // interrupt pending
|
||||
BitField<u32, u8, 28, 2> CE; // coprocessor number if caused by a coprocessor
|
||||
BitField<u32, bool, 31, 1> BD; // exception occurred in branch delay slot, but pushed IP is for branch
|
||||
|
||||
static constexpr u32 WRITE_MASK = 0b0000'0000'0000'0000'0000'0011'0000'0000;
|
||||
} cause;
|
||||
|
||||
union DCIC
|
||||
{
|
||||
u32 bits;
|
||||
BitField<u32, bool, 0, 1> status_any_break;
|
||||
BitField<u32, bool, 1, 1> status_bpc_code_break;
|
||||
BitField<u32, bool, 2, 1> status_bda_data_break;
|
||||
BitField<u32, bool, 3, 1> status_bda_data_read_break;
|
||||
BitField<u32, bool, 4, 1> status_bda_data_write_break;
|
||||
BitField<u32, bool, 5, 1> status_any_jump_break;
|
||||
BitField<u32, u8, 12, 2> jump_redirection;
|
||||
BitField<u32, bool, 23, 1> super_master_enable_1;
|
||||
BitField<u32, bool, 24, 1> execution_breakpoint_enable;
|
||||
BitField<u32, bool, 25, 1> data_access_breakpoint;
|
||||
BitField<u32, bool, 26, 1> break_on_data_read;
|
||||
BitField<u32, bool, 27, 1> break_on_data_write;
|
||||
BitField<u32, bool, 28, 1> break_on_any_jump;
|
||||
BitField<u32, bool, 29, 1> master_enable_any_jump;
|
||||
BitField<u32, bool, 30, 1> master_enable_break;
|
||||
BitField<u32, bool, 31, 1> super_master_enable_2;
|
||||
|
||||
static constexpr u32 WRITE_MASK = 0b1111'1111'1000'0000'1111'0000'0011'1111;
|
||||
} dcic;
|
||||
};
|
||||
|
||||
} // namespace CPU
|
56
src/pse/dma.cpp
Normal file
56
src/pse/dma.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "dma.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "bus.h"
|
||||
Log_SetChannel(DMA);
|
||||
|
||||
DMA::DMA() = default;
|
||||
|
||||
DMA::~DMA() = default;
|
||||
|
||||
bool DMA::Initialize(Bus* bus, GPU* gpu)
|
||||
{
|
||||
m_bus = bus;
|
||||
m_gpu = gpu;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DMA::Reset()
|
||||
{
|
||||
m_state = {};
|
||||
m_DPCR.bits = 0;
|
||||
m_DCIR = 0;
|
||||
}
|
||||
|
||||
u32 DMA::ReadRegister(u32 offset)
|
||||
{
|
||||
const u32 channel_index = offset >> 4;
|
||||
if (channel_index < 7)
|
||||
{
|
||||
switch (offset & UINT32_C(0x0F))
|
||||
{
|
||||
case 0x00:
|
||||
return m_state[channel_index].base_address;
|
||||
case 0x04:
|
||||
return m_state[channel_index].block_control.bits;
|
||||
case 0x08:
|
||||
return m_state[channel_index].channel_control.bits;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (offset == 0x70)
|
||||
return m_DPCR.bits;
|
||||
else if (offset == 0x74)
|
||||
return m_DCIR;
|
||||
}
|
||||
|
||||
Log_ErrorPrintf("Unhandled register read: %02X", offset);
|
||||
return UINT32_C(0xFFFFFFFF);
|
||||
}
|
||||
|
||||
void DMA::WriteRegister(u32 offset, u32 value)
|
||||
{
|
||||
Log_ErrorPrintf("Unhandled register write: %02X <- %08X", offset, value);
|
||||
}
|
95
src/pse/dma.h
Normal file
95
src/pse/dma.h
Normal file
@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
|
||||
class Bus;
|
||||
class GPU;
|
||||
|
||||
class DMA
|
||||
{
|
||||
public:
|
||||
enum : u32
|
||||
{
|
||||
NUM_CHANNELS = 7
|
||||
};
|
||||
|
||||
enum class Channel : u32
|
||||
{
|
||||
MDECin = 0,
|
||||
MDECout = 1,
|
||||
GPU = 2,
|
||||
CDROM = 3,
|
||||
SPU = 4,
|
||||
PIO = 5,
|
||||
OTC = 6
|
||||
};
|
||||
|
||||
DMA();
|
||||
~DMA();
|
||||
|
||||
bool Initialize(Bus* bus, GPU* gpu);
|
||||
void Reset();
|
||||
|
||||
u32 ReadRegister(u32 offset);
|
||||
void WriteRegister(u32 offset, u32 value);
|
||||
|
||||
private:
|
||||
Bus* m_bus = nullptr;
|
||||
GPU* m_gpu = nullptr;
|
||||
|
||||
enum class SyncMode : u32
|
||||
{
|
||||
Word = 0,
|
||||
Block = 1,
|
||||
LinkedList = 2,
|
||||
Reserved = 3
|
||||
};
|
||||
|
||||
struct ChannelState
|
||||
{
|
||||
u32 base_address;
|
||||
|
||||
union BlockControl
|
||||
{
|
||||
u32 bits;
|
||||
struct
|
||||
{
|
||||
BitField<u32, u32, 0, 16> word_count;
|
||||
} word_mode;
|
||||
struct
|
||||
{
|
||||
BitField<u32, u32, 0, 16> block_size;
|
||||
BitField<u32, u32, 16, 16> block_count;
|
||||
} block_mode;
|
||||
} block_control;
|
||||
|
||||
union ChannelControl
|
||||
{
|
||||
u32 bits;
|
||||
BitField<u32, bool, 0, 1> direction_to_ram;
|
||||
BitField<u32, bool, 1, 1> address_step_forward;
|
||||
BitField<u32, bool, 8, 1> chopping_enable;
|
||||
BitField<u32, SyncMode, 9, 2> sync_mode;
|
||||
BitField<u32, u32, 16, 3> chopping_dma_window_size;
|
||||
BitField<u32, u32, 20, 3> chopping_cpu_window_size;
|
||||
BitField<u32, bool, 28, 1> start_trigger;
|
||||
} channel_control;
|
||||
};
|
||||
|
||||
std::array<ChannelState, NUM_CHANNELS> m_state = {};
|
||||
|
||||
struct DPCR
|
||||
{
|
||||
u32 bits;
|
||||
|
||||
u8 GetPriority(Channel channel) { return ((bits >> (static_cast<u8>(channel) * 4)) & u32(3)); }
|
||||
bool GetMasterEnable(Channel channel)
|
||||
{
|
||||
return ConvertToBoolUnchecked((bits >> (static_cast<u8>(channel) * 4 + 3)) & u32(1));
|
||||
}
|
||||
} m_DPCR;
|
||||
|
||||
static constexpr u32 DCIR_WRITE_MASK = 0b11111111'11111111'10000000'00111111;
|
||||
u32 m_DCIR = 0;
|
||||
};
|
443
src/pse/pse.vcxproj
Normal file
443
src/pse/pse.vcxproj
Normal file
@ -0,0 +1,443 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project InitialTargets="UNDUPOBJ" DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="DebugFast|Win32">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="DebugFast|x64">
|
||||
<Configuration>DebugFast</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|Win32">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="ReleaseLTCG|x64">
|
||||
<Configuration>ReleaseLTCG</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="bus.cpp" />
|
||||
<ClCompile Include="cpu_core.cpp" />
|
||||
<ClCompile Include="cpu_disasm.cpp" />
|
||||
<ClCompile Include="dma.cpp" />
|
||||
<ClCompile Include="system.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bus.h" />
|
||||
<ClInclude Include="cpu_core.h" />
|
||||
<ClInclude Include="cpu_disasm.h" />
|
||||
<ClInclude Include="cpu_types.h" />
|
||||
<ClInclude Include="dma.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
<ClInclude Include="types.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\dep\libsamplerate\libsamplerate.vcxproj">
|
||||
<Project>{2f2a2b7b-60b3-478c-921e-3633b3c45c3f}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\dep\YBaseLib\Source\YBaseLib.vcxproj">
|
||||
<Project>{b56ce698-7300-4fa5-9609-942f1d05c5a2}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\common\common.vcxproj">
|
||||
<Project>{ee054e08-3799-4a59-a422-18259c105ffd}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="cpu_core.inl" />
|
||||
<None Include="bus.inl" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{868B98C8-65A1-494B-8346-250A73A48C0A}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>pse</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<IntDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</IntDir>
|
||||
<TargetName>$(ProjectName)-$(Platform)-$(Configuration)</TargetName>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugFast|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<SupportJustMyCode>false</SupportJustMyCode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64-debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib32;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseLTCG|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>ENABLE_VOODOO=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\YBaseLib\Include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\softfloat\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\inih\cpp;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>SDL2.lib;SDL2main.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)dep\lib64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
<!-- ================ UNDUPOBJ ================ -->
|
||||
<!-- relevant topics -->
|
||||
<!-- https://stackoverflow.com/questions/3729515/visual-studio-2010-2008-cant-handle-source-files-with-identical-names-in-diff/26935613 -->
|
||||
<!-- https://stackoverflow.com/questions/7033855/msvc10-mp-builds-not-multicore-across-folders-in-a-project -->
|
||||
<!-- https://stackoverflow.com/questions/18304911/how-can-one-modify-an-itemdefinitiongroup-from-an-msbuild-target -->
|
||||
<!-- other maybe related info -->
|
||||
<!-- https://stackoverflow.com/questions/841913/modify-msbuild-itemgroup-metadata -->
|
||||
<UsingTask TaskName="UNDUPOBJ_TASK" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<OutputDir ParameterType="System.String" Required="true" />
|
||||
<ItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
|
||||
<OutputItemList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Code><![CDATA[
|
||||
//general outline: for each item (in ClCompile) assign it to a subdirectory of $(IntDir) by allocating subdirectories 0,1,2, etc., as needed to prevent duplicate filenames from clobbering each other
|
||||
//this minimizes the number of batches that need to be run, since each subdirectory will necessarily be in a distinct batch due to /Fo specifying that output subdirectory
|
||||
|
||||
var assignmentMap = new Dictionary<string,int>();
|
||||
HashSet<string> neededDirectories = new HashSet<string>();
|
||||
foreach( var item in ItemList )
|
||||
{
|
||||
//solve bug e.g. Checkbox.cpp vs CheckBox.cpp
|
||||
var filename = item.GetMetadata("Filename").ToUpperInvariant();
|
||||
|
||||
//assign reused filenames to increasing numbers
|
||||
//assign previously unused filenames to 0
|
||||
int assignment = 0;
|
||||
if(assignmentMap.TryGetValue(filename, out assignment))
|
||||
assignmentMap[filename] = ++assignment;
|
||||
else
|
||||
assignmentMap[filename] = 0;
|
||||
|
||||
var thisFileOutdir = Path.Combine(OutputDir,assignment.ToString()) + "/"; //take care it ends in / so /Fo knows it's a directory and not a filename
|
||||
item.SetMetadata( "ObjectFileName", thisFileOutdir );
|
||||
}
|
||||
|
||||
foreach(var needed in neededDirectories)
|
||||
System.IO.Directory.CreateDirectory(needed);
|
||||
|
||||
OutputItemList = ItemList;
|
||||
ItemList = new Microsoft.Build.Framework.ITaskItem[0];
|
||||
|
||||
]]></Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
<Target Name="UNDUPOBJ">
|
||||
<!-- see stackoverflow topics for discussion on why we need to do some loopy copying stuff here -->
|
||||
<ItemGroup>
|
||||
<ClCompileCopy Include="@(ClCompile)" />
|
||||
<ClCompile Remove="@(ClCompile)" />
|
||||
</ItemGroup>
|
||||
<UNDUPOBJ_TASK OutputDir="$(IntDir)" ItemList="@(ClCompileCopy)" OutputItemList="@(ClCompile)">
|
||||
<Output ItemName="ClCompile" TaskParameter="OutputItemList" />
|
||||
</UNDUPOBJ_TASK>
|
||||
</Target>
|
||||
<!-- ================ UNDUPOBJ ================ -->
|
||||
<PropertyGroup Label="Globals">
|
||||
<IgnoreWarnCompileDuplicatedFilename>true</IgnoreWarnCompileDuplicatedFilename>
|
||||
</PropertyGroup>
|
||||
</Project>
|
24
src/pse/pse.vcxproj.filters
Normal file
24
src/pse/pse.vcxproj.filters
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="system.cpp" />
|
||||
<ClCompile Include="cpu_core.cpp" />
|
||||
<ClCompile Include="cpu_disasm.cpp" />
|
||||
<ClCompile Include="bus.cpp" />
|
||||
<ClCompile Include="dma.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="types.h" />
|
||||
<ClInclude Include="save_state_version.h" />
|
||||
<ClInclude Include="system.h" />
|
||||
<ClInclude Include="cpu_core.h" />
|
||||
<ClInclude Include="cpu_types.h" />
|
||||
<ClInclude Include="cpu_disasm.h" />
|
||||
<ClInclude Include="bus.h" />
|
||||
<ClInclude Include="dma.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="cpu_core.inl" />
|
||||
<None Include="bus.inl" />
|
||||
</ItemGroup>
|
||||
</Project>
|
4
src/pse/save_state_version.h
Normal file
4
src/pse/save_state_version.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "pse/types.h"
|
||||
|
||||
constexpr u32 SAVE_STATE_VERSION = 1;
|
30
src/pse/system.cpp
Normal file
30
src/pse/system.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "system.h"
|
||||
|
||||
System::System() = default;
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
bool System::Initialize()
|
||||
{
|
||||
if (!m_cpu.Initialize(&m_bus))
|
||||
return false;
|
||||
|
||||
if (!m_bus.Initialize(this, &m_dma, nullptr))
|
||||
return false;
|
||||
|
||||
if (!m_dma.Initialize(&m_bus, nullptr))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void System::Reset()
|
||||
{
|
||||
m_cpu.Reset();
|
||||
m_bus.Reset();
|
||||
}
|
||||
|
||||
void System::RunFrame()
|
||||
{
|
||||
m_cpu.Execute();
|
||||
}
|
22
src/pse/system.h
Normal file
22
src/pse/system.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "bus.h"
|
||||
#include "dma.h"
|
||||
#include "cpu_core.h"
|
||||
#include "types.h"
|
||||
|
||||
class System
|
||||
{
|
||||
public:
|
||||
System();
|
||||
~System();
|
||||
|
||||
bool Initialize();
|
||||
void Reset();
|
||||
|
||||
void RunFrame();
|
||||
|
||||
private:
|
||||
CPU::Core m_cpu;
|
||||
Bus m_bus;
|
||||
DMA m_dma;
|
||||
};
|
18
src/pse/types.h
Normal file
18
src/pse/types.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "common/types.h"
|
||||
|
||||
// Physical memory addresses are 32-bits wide
|
||||
using PhysicalMemoryAddress = u32;
|
||||
using VirtualMemoryAddress = u32;
|
||||
|
||||
enum class MemoryAccessType : u32
|
||||
{
|
||||
Read,
|
||||
Write
|
||||
};
|
||||
enum class MemoryAccessSize : u32
|
||||
{
|
||||
Byte,
|
||||
HalfWord,
|
||||
Word
|
||||
};
|
Reference in New Issue
Block a user