Simple UI for frame seeking.
This commit is contained in:
parent
5227fe72b5
commit
07c592942f
|
@ -17,22 +17,21 @@ namespace poly {
|
|||
namespace fs {
|
||||
|
||||
std::string CanonicalizePath(const std::string& original_path) {
|
||||
char path_separator('\\');
|
||||
std::string path(poly::fix_path_separators(original_path, path_separator));
|
||||
char path_sep('\\');
|
||||
std::string path(poly::fix_path_separators(original_path, path_sep));
|
||||
|
||||
std::vector<std::string::size_type> path_breaks;
|
||||
|
||||
std::string::size_type pos(path.find_first_of(path_separator));
|
||||
std::string::size_type pos(path.find_first_of(path_sep));
|
||||
std::string::size_type pos_n(std::string::npos);
|
||||
|
||||
while (pos != std::string::npos) {
|
||||
if ((pos_n = path.find_first_of(path_separator, pos + 1)) == std::string::npos) {
|
||||
if ((pos_n = path.find_first_of(path_sep, pos + 1)) == std::string::npos) {
|
||||
pos_n = path.size();
|
||||
}
|
||||
|
||||
auto diff(pos_n - pos);
|
||||
switch (diff)
|
||||
{
|
||||
switch (diff) {
|
||||
case 0:
|
||||
pos_n = std::string::npos;
|
||||
break;
|
||||
|
@ -46,14 +45,13 @@ std::string CanonicalizePath(const std::string& original_path) {
|
|||
if (path[pos + 1] == '.') {
|
||||
path.erase(pos, 2);
|
||||
pos_n -= 2;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
path_breaks.push_back(pos);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// Potential marker for parent directory
|
||||
if (path[pos + 1] == '.' && path[pos + 2] == '.'){
|
||||
if (path[pos + 1] == '.' && path[pos + 2] == '.') {
|
||||
if (path_breaks.empty()) {
|
||||
// Ensure we don't override the device name
|
||||
std::string::size_type loc(path.find_first_of(':'));
|
||||
|
@ -61,13 +59,11 @@ std::string CanonicalizePath(const std::string& original_path) {
|
|||
if (loc == std::string::npos || loc > req) {
|
||||
path.erase(0, req);
|
||||
pos_n -= req;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
path.erase(loc + 1, req - (loc + 1));
|
||||
pos_n -= req - (loc + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
auto last(path_breaks.back());
|
||||
auto last_diff((pos + 3) - last);
|
||||
path.erase(last, last_diff);
|
||||
|
@ -75,8 +71,7 @@ std::string CanonicalizePath(const std::string& original_path) {
|
|||
// Also remove path reference
|
||||
path_breaks.erase(path_breaks.end() - 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
path_breaks.push_back(pos);
|
||||
}
|
||||
break;
|
||||
|
@ -90,13 +85,13 @@ std::string CanonicalizePath(const std::string& original_path) {
|
|||
}
|
||||
|
||||
// Remove trailing seperator
|
||||
if (!path.empty() && path.back() == path_separator) {
|
||||
if (!path.empty() && path.back() == path_sep) {
|
||||
path.erase(path.size() - 1);
|
||||
}
|
||||
|
||||
// Final sanity check for dead paths
|
||||
if ((path.size() == 1 && (path[0] == '.' || path[0] == path_separator))
|
||||
|| (path.size() == 2 && path[0] == '.' && path[1] == '.')) {
|
||||
if ((path.size() == 1 && (path[0] == '.' || path[0] == path_sep)) ||
|
||||
(path.size() == 2 && path[0] == '.' && path[1] == '.')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -106,25 +101,19 @@ std::string CanonicalizePath(const std::string& original_path) {
|
|||
WildcardFlags WildcardFlags::FIRST(true, false);
|
||||
WildcardFlags WildcardFlags::LAST(false, true);
|
||||
|
||||
WildcardFlags::WildcardFlags()
|
||||
: FromStart(false)
|
||||
, ToEnd(false)
|
||||
{ }
|
||||
WildcardFlags::WildcardFlags() : FromStart(false), ToEnd(false) {}
|
||||
|
||||
WildcardFlags::WildcardFlags(bool start, bool end)
|
||||
: FromStart(start)
|
||||
, ToEnd(end)
|
||||
{ }
|
||||
: FromStart(start), ToEnd(end) {}
|
||||
|
||||
WildcardRule::WildcardRule(const std::string& str_match, const WildcardFlags& flags)
|
||||
: match(str_match)
|
||||
, rules(flags)
|
||||
{
|
||||
WildcardRule::WildcardRule(const std::string& str_match,
|
||||
const WildcardFlags& flags)
|
||||
: match(str_match), rules(flags) {
|
||||
std::transform(match.begin(), match.end(), match.begin(), tolower);
|
||||
}
|
||||
|
||||
bool WildcardRule::Check(const std::string& str_lower, std::string::size_type& offset) const
|
||||
{
|
||||
bool WildcardRule::Check(const std::string& str_lower,
|
||||
std::string::size_type& offset) const {
|
||||
if (match.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -151,8 +140,7 @@ bool WildcardRule::Check(const std::string& str_lower, std::string::size_type& o
|
|||
return false;
|
||||
}
|
||||
|
||||
void WildcardEngine::PreparePattern(const std::string& pattern)
|
||||
{
|
||||
void WildcardEngine::PreparePattern(const std::string& pattern) {
|
||||
rules.clear();
|
||||
|
||||
WildcardFlags flags(WildcardFlags::FIRST);
|
||||
|
@ -172,13 +160,11 @@ void WildcardEngine::PreparePattern(const std::string& pattern)
|
|||
}
|
||||
}
|
||||
|
||||
void WildcardEngine::SetRule(const std::string& pattern)
|
||||
{
|
||||
void WildcardEngine::SetRule(const std::string& pattern) {
|
||||
PreparePattern(pattern);
|
||||
}
|
||||
|
||||
bool WildcardEngine::Match(const std::string& str) const
|
||||
{
|
||||
bool WildcardEngine::Match(const std::string& str) const {
|
||||
std::string str_lc;
|
||||
std::transform(str.begin(), str.end(), std::back_inserter(str_lc), tolower);
|
||||
|
||||
|
|
|
@ -126,16 +126,13 @@ std::string fix_path_separators(const std::string& source, char new_sep) {
|
|||
return dest;
|
||||
}
|
||||
|
||||
std::string find_name_from_path(const std::string& path)
|
||||
{
|
||||
std::string find_name_from_path(const std::string& path) {
|
||||
std::string name(path);
|
||||
|
||||
if (!path.empty()) {
|
||||
std::string::size_type from(std::string::npos);
|
||||
if (path.back() == '\\') {
|
||||
from = path.size() - 2;
|
||||
}
|
||||
|
||||
auto pos(path.find_last_of('\\', from));
|
||||
if (pos != std::string::npos) {
|
||||
if (from == std::string::npos) {
|
||||
|
@ -146,7 +143,26 @@ std::string find_name_from_path(const std::string& path)
|
|||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
std::wstring find_name_from_path(const std::wstring& path) {
|
||||
std::wstring name(path);
|
||||
if (!path.empty()) {
|
||||
std::wstring::size_type from(std::wstring::npos);
|
||||
if (path.back() == '\\') {
|
||||
from = path.size() - 2;
|
||||
}
|
||||
auto pos(path.find_last_of('\\', from));
|
||||
if (pos != std::wstring::npos) {
|
||||
if (from == std::wstring::npos) {
|
||||
name = path.substr(pos + 1);
|
||||
} else {
|
||||
auto len(from - pos);
|
||||
name = path.substr(pos + 1, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ std::string fix_path_separators(const std::string& source,
|
|||
|
||||
// Find the top directory name or filename from a path
|
||||
std::string find_name_from_path(const std::string& path);
|
||||
std::wstring find_name_from_path(const std::wstring& path);
|
||||
|
||||
} // namespace poly
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ class Fence {
|
|||
|
||||
// Gets the current high-performance tick count.
|
||||
uint64_t ticks();
|
||||
uint64_t ticks_per_second();
|
||||
|
||||
// TODO(benvanik): processor info API.
|
||||
|
||||
|
|
|
@ -23,6 +23,14 @@ uint64_t ticks() {
|
|||
return time;
|
||||
}
|
||||
|
||||
uint64_t ticks_per_second() {
|
||||
static LARGE_INTEGER freq = {0};
|
||||
if (!freq.QuadPart) {
|
||||
QueryPerformanceFrequency(&freq);
|
||||
}
|
||||
return freq.QuadPart;
|
||||
}
|
||||
|
||||
uint32_t current_thread_id() {
|
||||
return static_cast<uint32_t>(GetCurrentThreadId());
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ void Control::OnResize(UIEvent& e) { on_resize(e); }
|
|||
|
||||
void Control::OnLayout(UIEvent& e) { on_layout(e); }
|
||||
|
||||
void Control::OnPaint(UIEvent& e) { on_paint(e); }
|
||||
|
||||
void Control::OnVisible(UIEvent& e) { on_visible(e); }
|
||||
|
||||
void Control::OnHidden(UIEvent& e) { on_hidden(e); }
|
||||
|
|
|
@ -31,6 +31,8 @@ class Control {
|
|||
|
||||
Control* parent() const { return parent_; }
|
||||
|
||||
size_t child_count() const { return children_.size(); }
|
||||
Control* child(size_t i) const { return children_[i].get(); }
|
||||
void AddChild(Control* child_control);
|
||||
void AddChild(std::unique_ptr<Control> child_control);
|
||||
void AddChild(ControlPtr child_control);
|
||||
|
@ -65,6 +67,7 @@ class Control {
|
|||
public:
|
||||
poly::Delegate<UIEvent> on_resize;
|
||||
poly::Delegate<UIEvent> on_layout;
|
||||
poly::Delegate<UIEvent> on_paint;
|
||||
|
||||
poly::Delegate<UIEvent> on_visible;
|
||||
poly::Delegate<UIEvent> on_hidden;
|
||||
|
@ -94,6 +97,7 @@ class Control {
|
|||
|
||||
virtual void OnResize(UIEvent& e);
|
||||
virtual void OnLayout(UIEvent& e);
|
||||
virtual void OnPaint(UIEvent& e);
|
||||
|
||||
virtual void OnVisible(UIEvent& e);
|
||||
virtual void OnHidden(UIEvent& e);
|
||||
|
|
|
@ -93,8 +93,6 @@ uint64_t CommandProcessor::QueryTime() {
|
|||
bool CommandProcessor::Initialize(std::unique_ptr<GLContext> context) {
|
||||
context_ = std::move(context);
|
||||
|
||||
pending_fn_event_ = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
||||
|
||||
worker_running_ = true;
|
||||
worker_thread_ = std::thread([this]() {
|
||||
poly::threading::set_name("GL4 Worker");
|
||||
|
@ -118,8 +116,6 @@ void CommandProcessor::Shutdown() {
|
|||
shader_cache_.clear();
|
||||
|
||||
context_.reset();
|
||||
|
||||
CloseHandle(pending_fn_event_);
|
||||
}
|
||||
|
||||
void CommandProcessor::BeginTracing(const std::wstring& root_path) {
|
||||
|
@ -130,10 +126,12 @@ void CommandProcessor::BeginTracing(const std::wstring& root_path) {
|
|||
void CommandProcessor::EndTracing() { trace_writer_.Close(); }
|
||||
|
||||
void CommandProcessor::CallInThread(std::function<void()> fn) {
|
||||
assert_null(pending_fn_);
|
||||
pending_fn_ = std::move(fn);
|
||||
WaitForSingleObject(pending_fn_event_, INFINITE);
|
||||
ResetEvent(pending_fn_event_);
|
||||
if (pending_fns_.empty() &&
|
||||
worker_thread_.get_id() == std::this_thread::get_id()) {
|
||||
fn();
|
||||
} else {
|
||||
pending_fns_.push(std::move(fn));
|
||||
}
|
||||
}
|
||||
|
||||
void CommandProcessor::WorkerMain() {
|
||||
|
@ -144,11 +142,10 @@ void CommandProcessor::WorkerMain() {
|
|||
}
|
||||
|
||||
while (worker_running_) {
|
||||
if (pending_fn_) {
|
||||
auto fn = std::move(pending_fn_);
|
||||
pending_fn_ = nullptr;
|
||||
while (!pending_fns_.empty()) {
|
||||
auto fn = std::move(pending_fns_.front());
|
||||
pending_fns_.pop();
|
||||
fn();
|
||||
SetEvent(pending_fn_event_);
|
||||
}
|
||||
|
||||
uint32_t write_ptr_index = write_ptr_index_.load();
|
||||
|
@ -166,10 +163,10 @@ void CommandProcessor::WorkerMain() {
|
|||
SwitchToThread();
|
||||
MemoryBarrier();
|
||||
write_ptr_index = write_ptr_index_.load();
|
||||
} while (!pending_fn_ && (write_ptr_index == 0xBAADF00D ||
|
||||
} while (pending_fns_.empty() && (write_ptr_index == 0xBAADF00D ||
|
||||
read_ptr_index_ == write_ptr_index));
|
||||
// ReturnFromWait();
|
||||
if (pending_fn_) {
|
||||
if (!pending_fns_.empty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
@ -212,8 +213,7 @@ class CommandProcessor {
|
|||
std::atomic<bool> worker_running_;
|
||||
std::unique_ptr<GLContext> context_;
|
||||
SwapHandler swap_handler_;
|
||||
std::function<void()> pending_fn_;
|
||||
HANDLE pending_fn_event_;
|
||||
std::queue<std::function<void()>> pending_fns_;
|
||||
|
||||
SwapMode swap_mode_;
|
||||
|
||||
|
|
|
@ -126,11 +126,13 @@ void GL4GraphicsSystem::RequestSwap() {
|
|||
command_processor_->CallInThread([&]() { command_processor_->IssueSwap(); });
|
||||
}
|
||||
|
||||
const uint8_t* GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data,
|
||||
size_t trace_size,
|
||||
void GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
command_processor_->CallInThread(
|
||||
[this, trace_data, trace_size, playback_mode]() {
|
||||
command_processor_->set_swap_mode(SwapMode::kIgnored);
|
||||
|
||||
auto trace_ptr = trace_data;
|
||||
command_processor_->CallInThread([&]() {
|
||||
bool pending_break = false;
|
||||
const PacketStartCommand* pending_packet = nullptr;
|
||||
while (trace_ptr < trace_data + trace_size) {
|
||||
|
@ -152,8 +154,8 @@ const uint8_t* GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data,
|
|||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
||||
auto cmd = reinterpret_cast<const IndirectBufferStartCommand*>(
|
||||
trace_ptr);
|
||||
//
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
|
@ -217,8 +219,9 @@ const uint8_t* GL4GraphicsSystem::PlayTrace(const uint8_t* trace_data,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
command_processor_->set_swap_mode(SwapMode::kNormal);
|
||||
});
|
||||
return trace_ptr;
|
||||
}
|
||||
|
||||
void GL4GraphicsSystem::MarkVblank() {
|
||||
|
|
|
@ -38,7 +38,7 @@ class GL4GraphicsSystem : public GraphicsSystem {
|
|||
|
||||
void RequestSwap() override;
|
||||
|
||||
const uint8_t* PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -91,6 +91,9 @@ LRESULT WGLControl::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
current_paint_callback_ = nullptr;
|
||||
}
|
||||
|
||||
poly::ui::UIEvent e(this);
|
||||
OnPaint(e);
|
||||
|
||||
// TODO(benvanik): profiler present.
|
||||
Profiler::Present();
|
||||
}
|
||||
|
|
|
@ -47,10 +47,8 @@ class GraphicsSystem {
|
|||
kUntilEnd,
|
||||
kBreakOnSwap,
|
||||
};
|
||||
virtual const uint8_t* PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {
|
||||
return nullptr;
|
||||
}
|
||||
virtual void PlayTrace(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode) {}
|
||||
|
||||
protected:
|
||||
GraphicsSystem();
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
*/
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "poly/main.h"
|
||||
#include "poly/mapped_memory.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/gpu/gl4/gl_context.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
#include "xenia/gpu/tracing.h"
|
||||
#include "xenia/emulator.h"
|
||||
|
@ -20,6 +23,271 @@ DEFINE_string(target_trace_file, "", "Specifies the trace file to load.");
|
|||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
// TODO(benvanik): move to tracing.h/cc
|
||||
|
||||
class TraceReader {
|
||||
public:
|
||||
struct Frame {
|
||||
const uint8_t* start_ptr;
|
||||
const uint8_t* end_ptr;
|
||||
int command_count;
|
||||
};
|
||||
|
||||
TraceReader() : trace_data_(nullptr), trace_size_(0) {}
|
||||
~TraceReader() = default;
|
||||
|
||||
const Frame* frame(int n) const { return &frames_[n]; }
|
||||
int frame_count() const { return int(frames_.size()); }
|
||||
|
||||
bool Open(const std::wstring& path) {
|
||||
Close();
|
||||
|
||||
mmap_ = poly::MappedMemory::Open(path, poly::MappedMemory::Mode::kRead);
|
||||
if (!mmap_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trace_data_ = reinterpret_cast<const uint8_t*>(mmap_->data());
|
||||
trace_size_ = mmap_->size();
|
||||
|
||||
ParseTrace();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
mmap_.reset();
|
||||
trace_data_ = nullptr;
|
||||
trace_size_ = 0;
|
||||
}
|
||||
|
||||
// void Foo() {
|
||||
// auto trace_ptr = trace_data;
|
||||
// while (trace_ptr < trace_data + trace_size) {
|
||||
// auto cmd_type = *reinterpret_cast<const TraceCommandType*>(trace_ptr);
|
||||
// switch (cmd_type) {
|
||||
// case TraceCommandType::kPrimaryBufferStart:
|
||||
// break;
|
||||
// case TraceCommandType::kPrimaryBufferEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kIndirectBufferStart:
|
||||
// break;
|
||||
// case TraceCommandType::kIndirectBufferEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kPacketStart:
|
||||
// break;
|
||||
// case TraceCommandType::kPacketEnd:
|
||||
// break;
|
||||
// case TraceCommandType::kMemoryRead:
|
||||
// break;
|
||||
// case TraceCommandType::kMemoryWrite:
|
||||
// break;
|
||||
// case TraceCommandType::kEvent:
|
||||
// break;
|
||||
// }
|
||||
// /*trace_ptr = graphics_system->PlayTrace(
|
||||
// trace_ptr, trace_size - (trace_ptr - trace_data),
|
||||
// GraphicsSystem::TracePlaybackMode::kBreakOnSwap);*/
|
||||
// }
|
||||
//}
|
||||
|
||||
protected:
|
||||
void ParseTrace() {
|
||||
auto trace_ptr = trace_data_;
|
||||
Frame current_frame = {
|
||||
trace_ptr, nullptr, 0,
|
||||
};
|
||||
bool pending_break = false;
|
||||
while (trace_ptr < trace_data_ + trace_size_) {
|
||||
++current_frame.command_count;
|
||||
auto type =
|
||||
static_cast<TraceCommandType>(poly::load<uint32_t>(trace_ptr));
|
||||
switch (type) {
|
||||
case TraceCommandType::kPrimaryBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const PrimaryBufferStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPrimaryBufferEnd: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const PrimaryBufferEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferStart: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kIndirectBufferEnd: {
|
||||
auto cmd =
|
||||
reinterpret_cast<const IndirectBufferEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketStart: {
|
||||
auto cmd = reinterpret_cast<const PacketStartCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->count * 4;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kPacketEnd: {
|
||||
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
if (pending_break) {
|
||||
current_frame.end_ptr = trace_ptr;
|
||||
frames_.push_back(std::move(current_frame));
|
||||
current_frame.start_ptr = trace_ptr;
|
||||
current_frame.end_ptr = nullptr;
|
||||
current_frame.command_count = 0;
|
||||
pending_break = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryRead: {
|
||||
auto cmd = reinterpret_cast<const MemoryReadCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kMemoryWrite: {
|
||||
auto cmd = reinterpret_cast<const MemoryWriteCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd) + cmd->length;
|
||||
break;
|
||||
}
|
||||
case TraceCommandType::kEvent: {
|
||||
auto cmd = reinterpret_cast<const EventCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
switch (cmd->event_type) {
|
||||
case EventType::kSwap: {
|
||||
pending_break = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pending_break) {
|
||||
current_frame.end_ptr = trace_ptr;
|
||||
frames_.push_back(std::move(current_frame));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<poly::MappedMemory> mmap_;
|
||||
const uint8_t* trace_data_;
|
||||
size_t trace_size_;
|
||||
std::vector<Frame> frames_;
|
||||
};
|
||||
|
||||
class TracePlayer : public TraceReader {
|
||||
public:
|
||||
TracePlayer(poly::ui::Loop* loop, GraphicsSystem* graphics_system)
|
||||
: loop_(loop),
|
||||
graphics_system_(graphics_system),
|
||||
current_frame_index_(0),
|
||||
current_command_index_(-1) {}
|
||||
~TracePlayer() = default;
|
||||
|
||||
int current_frame_index() const { return current_frame_index_; }
|
||||
|
||||
const Frame* current_frame() const {
|
||||
if (current_frame_index_ > frame_count()) {
|
||||
return nullptr;
|
||||
}
|
||||
return frame(current_frame_index_);
|
||||
}
|
||||
|
||||
void SeekFrame(int target_frame) {
|
||||
if (current_frame_index_ == target_frame) {
|
||||
return;
|
||||
}
|
||||
current_frame_index_ = target_frame;
|
||||
auto frame = current_frame();
|
||||
current_command_index_ = frame->command_count - 1;
|
||||
|
||||
graphics_system_->PlayTrace(
|
||||
frame->start_ptr, frame->end_ptr - frame->start_ptr,
|
||||
GraphicsSystem::TracePlaybackMode::kBreakOnSwap);
|
||||
}
|
||||
|
||||
int current_command_index() const { return current_command_index_; }
|
||||
|
||||
void SeekCommand(int target_command) {
|
||||
current_command_index_ = target_command;
|
||||
}
|
||||
|
||||
private:
|
||||
poly::ui::Loop* loop_;
|
||||
GraphicsSystem* graphics_system_;
|
||||
int current_frame_index_;
|
||||
int current_command_index_;
|
||||
};
|
||||
|
||||
void DrawUI(xe::ui::MainWindow* window, TracePlayer& player) {
|
||||
ImGui::ShowTestWindow();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(5, 5), ImGuiSetCondition_FirstUseEver);
|
||||
if (ImGui::Begin("Controller", nullptr, ImVec2(0, 0), -1.0f,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
int target_frame = player.current_frame_index();
|
||||
if (ImGui::Button("|<<")) {
|
||||
target_frame = 0;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(">>", ImVec2(0, 0), true)) {
|
||||
if (target_frame + 1 < player.frame_count()) {
|
||||
++target_frame;
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(">>|")) {
|
||||
target_frame = player.frame_count() - 1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SliderInt("", &target_frame, 0, player.frame_count() - 1);
|
||||
if (target_frame != player.current_frame_index()) {
|
||||
player.SeekFrame(target_frame);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(float(window->width()) - 500 - 5, 5),
|
||||
ImGuiSetCondition_FirstUseEver);
|
||||
if (ImGui::Begin("Frame Inspector", nullptr, ImVec2(500, 300))) {
|
||||
ImGui::Columns(2, "frame_inspector");
|
||||
ImGui::Text("Frame #%d", player.current_frame_index());
|
||||
ImGui::Separator();
|
||||
ImGui::BeginChild("Sub1");
|
||||
ImGui::PushID(-1);
|
||||
bool is_selected = player.current_command_index() == -1;
|
||||
if (ImGui::Selectable("<start>", &is_selected)) {
|
||||
player.SeekCommand(-1);
|
||||
}
|
||||
ImGui::PopID();
|
||||
int column_width = int(ImGui::GetContentRegionMax().x);
|
||||
auto frame = player.current_frame();
|
||||
for (int i = 0; i < frame->command_count; ++i) {
|
||||
ImGui::PushID(i);
|
||||
is_selected = i == player.current_command_index();
|
||||
if (ImGui::Selectable("command", &is_selected)) {
|
||||
player.SeekCommand(i);
|
||||
}
|
||||
ImGui::SameLine(column_width - 30);
|
||||
ImGui::Text("bar");
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("right side");
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImImpl_Setup();
|
||||
void ImImpl_Shutdown();
|
||||
|
||||
int trace_viewer_main(std::vector<std::wstring>& args) {
|
||||
// Create the emulator.
|
||||
auto emulator = std::make_unique<Emulator>(L"");
|
||||
|
@ -42,30 +310,336 @@ int trace_viewer_main(std::vector<std::wstring>& args) {
|
|||
path = args[1];
|
||||
}
|
||||
// Normalize the path and make absolute.
|
||||
std::wstring abs_path = poly::to_absolute_path(path);
|
||||
auto abs_path = poly::to_absolute_path(path);
|
||||
|
||||
auto window = emulator->main_window();
|
||||
auto loop = window->loop();
|
||||
auto file_name = poly::find_name_from_path(path);
|
||||
window->set_title(std::wstring(L"Xenia GPU Trace Viewer: ") + file_name);
|
||||
|
||||
// TODO(benvanik): UI? replay control on graphics system?
|
||||
auto graphics_system = emulator->graphics_system();
|
||||
auto mmap =
|
||||
poly::MappedMemory::Open(abs_path, poly::MappedMemory::Mode::kRead);
|
||||
auto trace_data = reinterpret_cast<const uint8_t*>(mmap->data());
|
||||
auto trace_size = mmap->size();
|
||||
|
||||
auto trace_ptr = trace_data;
|
||||
while (trace_ptr < trace_data + trace_size) {
|
||||
trace_ptr = graphics_system->PlayTrace(
|
||||
trace_ptr, trace_size - (trace_ptr - trace_data),
|
||||
GraphicsSystem::TracePlaybackMode::kBreakOnSwap);
|
||||
TracePlayer player(loop, emulator->graphics_system());
|
||||
if (!player.Open(abs_path)) {
|
||||
XELOGE("Could not load trace file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto control = window->child(0);
|
||||
control->on_key_down.AddListener([](poly::ui::KeyEvent& e) {});
|
||||
control->on_key_up.AddListener([](poly::ui::KeyEvent& e) {});
|
||||
control->on_mouse_down.AddListener([](poly::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case poly::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = true;
|
||||
break;
|
||||
case poly::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
control->on_mouse_move.AddListener([](poly::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
});
|
||||
control->on_mouse_up.AddListener([](poly::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
switch (e.button()) {
|
||||
case poly::ui::MouseEvent::Button::kLeft:
|
||||
io.MouseDown[0] = false;
|
||||
break;
|
||||
case poly::ui::MouseEvent::Button::kRight:
|
||||
io.MouseDown[1] = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
control->on_mouse_wheel.AddListener([](poly::ui::MouseEvent& e) {
|
||||
auto& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2(float(e.x()), float(e.y()));
|
||||
io.MouseWheel += float(e.dy() / 120.0f);
|
||||
});
|
||||
|
||||
control->on_paint.AddListener([&](poly::ui::UIEvent& e) {
|
||||
static bool imgui_setup = false;
|
||||
if (!imgui_setup) {
|
||||
ImImpl_Setup();
|
||||
imgui_setup = true;
|
||||
}
|
||||
auto& io = ImGui::GetIO();
|
||||
auto current_ticks = poly::threading::ticks();
|
||||
static uint64_t last_ticks = 0;
|
||||
io.DeltaTime = (current_ticks - last_ticks) /
|
||||
float(poly::threading::ticks_per_second());
|
||||
last_ticks = current_ticks;
|
||||
|
||||
io.DisplaySize =
|
||||
ImVec2(float(e.control()->width()), float(e.control()->height()));
|
||||
|
||||
ImGui::NewFrame();
|
||||
|
||||
DrawUI(window, player);
|
||||
|
||||
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
|
||||
ImGui::Render();
|
||||
|
||||
graphics_system->RequestSwap();
|
||||
});
|
||||
graphics_system->RequestSwap();
|
||||
|
||||
// Wait until we are exited.
|
||||
emulator->main_window()->loop()->AwaitQuit();
|
||||
|
||||
ImImpl_Shutdown();
|
||||
}
|
||||
|
||||
emulator.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO(benvanik): move to another file.
|
||||
|
||||
extern "C" GLEWContext* glewGetContext();
|
||||
extern "C" WGLEWContext* wglewGetContext();
|
||||
|
||||
static int shader_handle, vert_handle, frag_handle;
|
||||
static int texture_location, proj_mtx_location;
|
||||
static int position_location, uv_location, colour_location;
|
||||
static size_t vbo_max_size = 20000;
|
||||
static unsigned int vbo_handle, vao_handle;
|
||||
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count);
|
||||
void ImImpl_Setup() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
const GLchar* vertex_shader =
|
||||
"#version 330\n"
|
||||
"uniform mat4 ProjMtx;\n"
|
||||
"in vec2 Position;\n"
|
||||
"in vec2 UV;\n"
|
||||
"in vec4 Color;\n"
|
||||
"out vec2 Frag_UV;\n"
|
||||
"out vec4 Frag_Color;\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
" Frag_UV = UV;\n"
|
||||
" Frag_Color = Color;\n"
|
||||
" gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
|
||||
"}\n";
|
||||
|
||||
const GLchar* fragment_shader =
|
||||
"#version 330\n"
|
||||
"uniform sampler2D Texture;\n"
|
||||
"in vec2 Frag_UV;\n"
|
||||
"in vec4 Frag_Color;\n"
|
||||
"out vec4 Out_Color;\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
" Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n"
|
||||
"}\n";
|
||||
|
||||
shader_handle = glCreateProgram();
|
||||
vert_handle = glCreateShader(GL_VERTEX_SHADER);
|
||||
frag_handle = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(vert_handle, 1, &vertex_shader, 0);
|
||||
glShaderSource(frag_handle, 1, &fragment_shader, 0);
|
||||
glCompileShader(vert_handle);
|
||||
glCompileShader(frag_handle);
|
||||
glAttachShader(shader_handle, vert_handle);
|
||||
glAttachShader(shader_handle, frag_handle);
|
||||
glLinkProgram(shader_handle);
|
||||
|
||||
texture_location = glGetUniformLocation(shader_handle, "Texture");
|
||||
proj_mtx_location = glGetUniformLocation(shader_handle, "ProjMtx");
|
||||
position_location = glGetAttribLocation(shader_handle, "Position");
|
||||
uv_location = glGetAttribLocation(shader_handle, "UV");
|
||||
colour_location = glGetAttribLocation(shader_handle, "Color");
|
||||
|
||||
glGenBuffers(1, &vbo_handle);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
|
||||
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_DYNAMIC_DRAW);
|
||||
|
||||
glGenVertexArrays(1, &vao_handle);
|
||||
glBindVertexArray(vao_handle);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
|
||||
glEnableVertexAttribArray(position_location);
|
||||
glEnableVertexAttribArray(uv_location);
|
||||
glEnableVertexAttribArray(colour_location);
|
||||
|
||||
glVertexAttribPointer(position_location, 2, GL_FLOAT, GL_FALSE,
|
||||
sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
|
||||
glVertexAttribPointer(uv_location, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert),
|
||||
(GLvoid*)offsetof(ImDrawVert, uv));
|
||||
glVertexAttribPointer(colour_location, 4, GL_UNSIGNED_BYTE, GL_TRUE,
|
||||
sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
|
||||
glBindVertexArray(0);
|
||||
glDisableVertexAttribArray(position_location);
|
||||
glDisableVertexAttribArray(uv_location);
|
||||
glDisableVertexAttribArray(colour_location);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(
|
||||
&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo
|
||||
// because it is more likely to be compatible
|
||||
// with user's existing shader.
|
||||
|
||||
GLuint tex_id;
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &tex_id);
|
||||
glTextureParameteri(tex_id, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(tex_id, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTextureStorage2D(tex_id, 1, GL_RGBA8, width, height);
|
||||
glTextureSubImage2D(tex_id, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
pixels);
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->TexID = (void*)(intptr_t)tex_id;
|
||||
|
||||
io.DeltaTime = 1.0f / 60.0f;
|
||||
io.RenderDrawListsFn = ImImpl_RenderDrawLists;
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
style.WindowRounding = 0;
|
||||
|
||||
style.Colors[ImGuiCol_Text] = ImVec4(0.89f, 0.90f, 0.90f, 1.00f);
|
||||
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
style.Colors[ImGuiCol_Border] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f);
|
||||
style.Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.22f);
|
||||
style.Colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 1.00f, 0.00f, 0.78f);
|
||||
style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.58f, 0.00f, 0.61f);
|
||||
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.00f, 0.40f, 0.11f, 0.59f);
|
||||
style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.68f, 0.00f, 0.68f);
|
||||
style.Colors[ImGuiCol_ScrollbarGrabHovered] =
|
||||
ImVec4(0.00f, 1.00f, 0.15f, 0.62f);
|
||||
style.Colors[ImGuiCol_ScrollbarGrabActive] =
|
||||
ImVec4(0.00f, 0.91f, 0.09f, 0.40f);
|
||||
style.Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f);
|
||||
style.Colors[ImGuiCol_CheckHovered] = ImVec4(0.23f, 0.64f, 0.13f, 0.45f);
|
||||
style.Colors[ImGuiCol_CheckActive] = ImVec4(0.21f, 0.93f, 0.13f, 0.55f);
|
||||
style.Colors[ImGuiCol_CheckMark] = ImVec4(0.74f, 0.90f, 0.72f, 0.50f);
|
||||
style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
|
||||
style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.34f, 0.75f, 0.11f, 1.00f);
|
||||
style.Colors[ImGuiCol_Button] = ImVec4(0.15f, 0.56f, 0.11f, 0.60f);
|
||||
style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.72f, 0.09f, 1.00f);
|
||||
style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.19f, 0.60f, 0.09f, 1.00f);
|
||||
style.Colors[ImGuiCol_Header] = ImVec4(0.00f, 0.40f, 0.00f, 0.71f);
|
||||
style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.60f, 0.26f, 0.80f);
|
||||
style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.00f, 0.75f, 0.00f, 0.80f);
|
||||
style.Colors[ImGuiCol_Column] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_ColumnHovered] = ImVec4(0.36f, 0.89f, 0.38f, 1.00f);
|
||||
style.Colors[ImGuiCol_ColumnActive] = ImVec4(0.13f, 0.50f, 0.11f, 1.00f);
|
||||
style.Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
|
||||
style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
|
||||
style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
|
||||
style.Colors[ImGuiCol_CloseButton] = ImVec4(0.00f, 0.72f, 0.00f, 0.96f);
|
||||
style.Colors[ImGuiCol_CloseButtonHovered] =
|
||||
ImVec4(0.38f, 1.00f, 0.42f, 0.60f);
|
||||
style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.56f, 1.00f, 0.64f, 1.00f);
|
||||
style.Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_PlotHistogramHovered] =
|
||||
ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
|
||||
style.Colors[ImGuiCol_TooltipBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f);
|
||||
}
|
||||
void ImImpl_Shutdown() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (vao_handle) glDeleteVertexArrays(1, &vao_handle);
|
||||
if (vbo_handle) glDeleteBuffers(1, &vbo_handle);
|
||||
glDetachShader(shader_handle, vert_handle);
|
||||
glDetachShader(shader_handle, frag_handle);
|
||||
glDeleteShader(vert_handle);
|
||||
glDeleteShader(frag_handle);
|
||||
glDeleteProgram(shader_handle);
|
||||
auto tex_id = static_cast<GLuint>(intptr_t(io.Fonts->TexID));
|
||||
glDeleteTextures(1, &tex_id);
|
||||
ImGui::Shutdown();
|
||||
}
|
||||
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
|
||||
if (cmd_lists_count == 0) return;
|
||||
|
||||
// Setup render state: alpha-blending enabled, no face culling, no depth
|
||||
// testing, scissor enabled
|
||||
glEnable(GL_BLEND);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
// Setup orthographic projection matrix
|
||||
const float width = ImGui::GetIO().DisplaySize.x;
|
||||
const float height = ImGui::GetIO().DisplaySize.y;
|
||||
const float ortho_projection[4][4] = {
|
||||
{2.0f / width, 0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 2.0f / -height, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, -1.0f, 0.0f},
|
||||
{-1.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
glProgramUniform1i(shader_handle, texture_location, 0);
|
||||
glProgramUniformMatrix4fv(shader_handle, proj_mtx_location, 1, GL_FALSE,
|
||||
&ortho_projection[0][0]);
|
||||
|
||||
// Grow our buffer according to what we need
|
||||
size_t total_vtx_count = 0;
|
||||
for (int n = 0; n < cmd_lists_count; n++)
|
||||
total_vtx_count += cmd_lists[n]->vtx_buffer.size();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
|
||||
size_t neededBufferSize = total_vtx_count * sizeof(ImDrawVert);
|
||||
if (neededBufferSize > vbo_max_size) {
|
||||
vbo_max_size = neededBufferSize + 5000; // Grow buffer
|
||||
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_STREAM_DRAW);
|
||||
}
|
||||
|
||||
// Copy and convert all vertices into a single contiguous buffer
|
||||
unsigned char* buffer_data =
|
||||
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
|
||||
if (!buffer_data) return;
|
||||
for (int n = 0; n < cmd_lists_count; n++) {
|
||||
const ImDrawList* cmd_list = cmd_lists[n];
|
||||
memcpy(buffer_data, &cmd_list->vtx_buffer[0],
|
||||
cmd_list->vtx_buffer.size() * sizeof(ImDrawVert));
|
||||
buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert);
|
||||
}
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(vao_handle);
|
||||
glUseProgram(shader_handle);
|
||||
|
||||
int cmd_offset = 0;
|
||||
ImTextureID prev_texture_id = 0;
|
||||
for (int n = 0; n < cmd_lists_count; n++) {
|
||||
const ImDrawList* cmd_list = cmd_lists[n];
|
||||
int vtx_offset = cmd_offset;
|
||||
const ImDrawCmd* pcmd_end = cmd_list->commands.end();
|
||||
for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end;
|
||||
pcmd++) {
|
||||
if (pcmd->texture_id != prev_texture_id) {
|
||||
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id);
|
||||
prev_texture_id = pcmd->texture_id;
|
||||
}
|
||||
glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w),
|
||||
(int)(pcmd->clip_rect.z - pcmd->clip_rect.x),
|
||||
(int)(pcmd->clip_rect.w - pcmd->clip_rect.y));
|
||||
glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count);
|
||||
vtx_offset += pcmd->vtx_count;
|
||||
}
|
||||
cmd_offset = vtx_offset;
|
||||
}
|
||||
|
||||
// Restore modified state
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
|
|
Loading…
Reference in New Issue