Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: cache frame and font metrics #16

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_library(reframework-d2d SHARED
src/DrawList.cpp
src/Plugin.cpp
src/D3D12CommandContext.cpp
src/D2DCommand.cpp
)
target_include_directories(reframework-d2d PRIVATE
src
Expand Down
16 changes: 16 additions & 0 deletions scripts/autorun/reframework-d2d.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ re.on_config_save(
end
)

re.on_frame(function ()
if not cfg.Debug then return end

imgui.text(string.format("Draw function cost: %.2f us", d2d.detail.get_draw_function_cost()))
imgui.text(string.format("Cache build cost: %.2f", d2d.detail.get_cache_build_cost()))
imgui.text(string.format("Draw calls: %d, cost: %.2f us", d2d.detail.get_draw_calls_count(), d2d.detail.get_draw_calls_cost()))
imgui.text(string.format("Render cost: %.2f us", d2d.detail.get_render_cost()))
local hit, miss = d2d.detail.get_cache_status()
imgui.text(string.format("Cache hit: %d, cache miss: %d", hit, miss))
end)

re.on_draw_ui(
function()
if not imgui.collapsing_header("REFramework D2D") then return end
Expand All @@ -14,6 +25,11 @@ re.on_draw_ui(
if changed then
cfg.max_update_rate = value
end

changed, value = imgui.checkbox("Debug", cfg.Debug)
if changed then
cfg.Debug = value
end

local last_error = d2d.detail.get_last_script_error()
if last_error ~= "" then
Expand Down
86 changes: 86 additions & 0 deletions src/D2DCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include "D2DCommand.hpp"

const float epsilon = 0.0001f;
auto float_equal = [](float a, float b) { return std::fabs(a - b) < epsilon; };

bool command_equals(const Command& lhs, const Command& rhs) {
if (lhs.type != rhs.type)
return false;

switch (lhs.type) {
case CommandType::TEXT:
return float_equal(lhs.text.x, rhs.text.x) && float_equal(lhs.text.y, rhs.text.y) && lhs.text.color == rhs.text.color &&
lhs.str == rhs.str && lhs.font_resource == rhs.font_resource;

case CommandType::FILL_RECT:
return float_equal(lhs.fill_rect.x, rhs.fill_rect.x) && float_equal(lhs.fill_rect.y, rhs.fill_rect.y) &&
float_equal(lhs.fill_rect.w, rhs.fill_rect.w) && float_equal(lhs.fill_rect.h, rhs.fill_rect.h) &&
lhs.fill_rect.color == rhs.fill_rect.color;

case CommandType::OUTLINE_RECT:
return float_equal(lhs.outline_rect.x, rhs.outline_rect.x) && float_equal(lhs.outline_rect.y, rhs.outline_rect.y) &&
float_equal(lhs.outline_rect.w, rhs.outline_rect.w) && float_equal(lhs.outline_rect.h, rhs.outline_rect.h) &&
float_equal(lhs.outline_rect.thickness, rhs.outline_rect.thickness) && lhs.outline_rect.color == rhs.outline_rect.color;

case CommandType::ROUNDED_RECT:
return float_equal(lhs.rounded_rect.x, rhs.rounded_rect.x) && float_equal(lhs.rounded_rect.y, rhs.rounded_rect.y) &&
float_equal(lhs.rounded_rect.w, rhs.rounded_rect.w) && float_equal(lhs.rounded_rect.h, rhs.rounded_rect.h) &&
float_equal(lhs.rounded_rect.rX, rhs.rounded_rect.rX) && float_equal(lhs.rounded_rect.rY, rhs.rounded_rect.rY) &&
float_equal(lhs.rounded_rect.thickness, rhs.rounded_rect.thickness) && lhs.rounded_rect.color == rhs.rounded_rect.color;

case CommandType::FILL_ROUNDED_RECT:
return float_equal(lhs.fill_rounded_rect.x, rhs.fill_rounded_rect.x) &&
float_equal(lhs.fill_rounded_rect.y, rhs.fill_rounded_rect.y) &&
float_equal(lhs.fill_rounded_rect.w, rhs.fill_rounded_rect.w) &&
float_equal(lhs.fill_rounded_rect.h, rhs.fill_rounded_rect.h) &&
float_equal(lhs.fill_rounded_rect.rX, rhs.fill_rounded_rect.rX) &&
float_equal(lhs.fill_rounded_rect.rY, rhs.fill_rounded_rect.rY) &&
lhs.fill_rounded_rect.color == rhs.fill_rounded_rect.color;

case CommandType::QUAD:
return float_equal(lhs.quad.x1, rhs.quad.x1) && float_equal(lhs.quad.y1, rhs.quad.y1) && float_equal(lhs.quad.x2, rhs.quad.x2) &&
float_equal(lhs.quad.y2, rhs.quad.y2) && float_equal(lhs.quad.x3, rhs.quad.x3) && float_equal(lhs.quad.y3, rhs.quad.y3) &&
float_equal(lhs.quad.x4, rhs.quad.x4) && float_equal(lhs.quad.y4, rhs.quad.y4) &&
float_equal(lhs.quad.thickness, rhs.quad.thickness) && lhs.quad.color == rhs.quad.color;

case CommandType::FILL_QUAD:
return float_equal(lhs.fill_quad.x1, rhs.fill_quad.x1) && float_equal(lhs.fill_quad.y1, rhs.fill_quad.y1) &&
float_equal(lhs.fill_quad.x2, rhs.fill_quad.x2) && float_equal(lhs.fill_quad.y2, rhs.fill_quad.y2) &&
float_equal(lhs.fill_quad.x3, rhs.fill_quad.x3) && float_equal(lhs.fill_quad.y3, rhs.fill_quad.y3) &&
float_equal(lhs.fill_quad.x4, rhs.fill_quad.x4) && float_equal(lhs.fill_quad.y4, rhs.fill_quad.y4) &&
lhs.fill_quad.color == rhs.fill_quad.color;

case CommandType::LINE:
return float_equal(lhs.line.x1, rhs.line.x1) && float_equal(lhs.line.y1, rhs.line.y1) && float_equal(lhs.line.x2, rhs.line.x2) &&
float_equal(lhs.line.y2, rhs.line.y2) && float_equal(lhs.line.thickness, rhs.line.thickness) &&
lhs.line.color == rhs.line.color;

case CommandType::IMAGE:
return float_equal(lhs.image.x, rhs.image.x) && float_equal(lhs.image.y, rhs.image.y) && float_equal(lhs.image.w, rhs.image.w) &&
float_equal(lhs.image.h, rhs.image.h) && lhs.image_resource == rhs.image_resource;

case CommandType::FILL_CIRCLE:
return float_equal(lhs.fill_circle.x, rhs.fill_circle.x) && float_equal(lhs.fill_circle.y, rhs.fill_circle.y) &&
float_equal(lhs.fill_circle.radiusX, rhs.fill_circle.radiusX) &&
float_equal(lhs.fill_circle.radiusY, rhs.fill_circle.radiusY) && lhs.fill_circle.color == rhs.fill_circle.color;

case CommandType::CIRCLE:
return float_equal(lhs.circle.x, rhs.circle.x) && float_equal(lhs.circle.y, rhs.circle.y) &&
float_equal(lhs.circle.radiusX, rhs.circle.radiusX) && float_equal(lhs.circle.radiusY, rhs.circle.radiusY) &&
float_equal(lhs.circle.thickness, rhs.circle.thickness) && lhs.circle.color == rhs.circle.color;

case CommandType::PIE:
return float_equal(lhs.pie.x, rhs.pie.x) && float_equal(lhs.pie.y, rhs.pie.y) && float_equal(lhs.pie.r, rhs.pie.r) &&
float_equal(lhs.pie.startAngle, rhs.pie.startAngle) && float_equal(lhs.pie.sweepAngle, rhs.pie.sweepAngle) &&
lhs.pie.color == rhs.pie.color && lhs.pie.clockwise == rhs.pie.clockwise;

case CommandType::RING:
return float_equal(lhs.ring.x, rhs.ring.x) && float_equal(lhs.ring.y, rhs.ring.y) &&
float_equal(lhs.ring.outerRadius, rhs.ring.outerRadius) && float_equal(lhs.ring.innerRadius, rhs.ring.innerRadius) &&
float_equal(lhs.ring.startAngle, rhs.ring.startAngle) && float_equal(lhs.ring.sweepAngle, rhs.ring.sweepAngle) &&
lhs.ring.color == rhs.ring.color && lhs.ring.clockwise == rhs.ring.clockwise;

default:
return false;
}
}
146 changes: 146 additions & 0 deletions src/D2DCommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#pragma once

#include <string>
#include <vector>

#include "D2DFont.hpp"
#include "D2DImage.hpp"

enum class CommandType {
NONE,
TEXT,
FILL_RECT,
OUTLINE_RECT,
ROUNDED_RECT,
FILL_ROUNDED_RECT,
QUAD,
FILL_QUAD,
LINE,
IMAGE,
FILL_CIRCLE,
CIRCLE,
PIE,
RING
};

struct Command {
CommandType type;
union {
struct {
float x{};
float y{};
unsigned int color{};
} text;
struct {
float x{};
float y{};
float w{};
float h{};
unsigned int color{};
} fill_rect;
struct {
float x{};
float y{};
float w{};
float h{};
float thickness{};
unsigned int color{};
} outline_rect;
struct {
float x{};
float y{};
float w{};
float h{};
float rX{};
float rY{};
float thickness{};
unsigned int color{};
} rounded_rect;
struct {
float x{};
float y{};
float w{};
float h{};
float rX{};
float rY{};
unsigned int color{};
} fill_rounded_rect;
struct {
float x1{};
float y1{};
float x2{};
float y2{};
float x3{};
float y3{};
float x4{};
float y4{};
float thickness{};
unsigned int color{};
} quad;
struct {
float x1{};
float y1{};
float x2{};
float y2{};
float x3{};
float y3{};
float x4{};
float y4{};
unsigned int color{};
} fill_quad;
struct {
float x1{};
float y1{};
float x2{};
float y2{};
float thickness{};
unsigned int color{};
} line;
struct {
float x{};
float y{};
float w{};
float h{};
} image;
struct {
float x{};
float y{};
float radiusX{};
float radiusY{};
unsigned int color{};
} fill_circle;
struct {
float x{};
float y{};
float radiusX{};
float radiusY{};
float thickness{};
unsigned int color{};
} circle;
struct {
float x{};
float y{};
float r{};
float startAngle{};
float sweepAngle{};
unsigned int color{};
bool clockwise{};
} pie;
struct {
float x{};
float y{};
float outerRadius{};
float innerRadius{};
float startAngle{};
float sweepAngle{};
unsigned int color{};
bool clockwise{};
} ring;
};
std::string str{};
std::shared_ptr<D2DFont> font_resource{};
std::shared_ptr<D2DImage> image_resource{};
};


bool command_equals(const Command& lhs, const Command& rhs);
11 changes: 9 additions & 2 deletions src/D2DFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@ D2DFont::ComPtr<IDWriteTextLayout> D2DFont::layout(const std::string& text) {
}

std::tuple<float, float> D2DFont::measure(const std::string& text) {
if (auto size = m_sizes.get(text)) {
return (*size).get();
}

DWRITE_TEXT_METRICS metrics{};

layout(text)->GetMetrics(&metrics);

return std::make_tuple(metrics.width, metrics.height);
}
auto size = std::make_tuple(metrics.width, metrics.height);
m_sizes.put(text, size);

return size;
}
3 changes: 2 additions & 1 deletion src/D2DFont.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ class D2DFont {
ComPtr<IDWriteFactory> m_dwrite{};
ComPtr<IDWriteTextFormat> m_format{};
LruCache<std::string, ComPtr<IDWriteTextLayout>> m_layouts{100};
};
LruCache<std::string, std::tuple<float, float>> m_sizes{100};
};
41 changes: 36 additions & 5 deletions src/D2DPainter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,41 @@ D2DPainter::D2DPainter(ID3D11Device* device, IDXGISurface* surface) {
}
}

void D2DPainter::init_cache(std::vector<Command>& commands) {
cache_hit = 0;
cache_miss = 0;
need_repaint = false;

if (commands.size() != cache_command_count) {
need_repaint = true;
}
cache_command_count = commands.size();

if (command_cache.size() < cache_command_count) {
command_cache.resize(cache_command_count);
need_repaint = true;
}

auto cache_index = 0;
for (auto&& cmd : commands) {
const CachedCommand& cache = command_cache[cache_index];
if (cache.command.type == cmd.type && command_equals(cmd, cache.command)) {
cache_hit++;
} else {
cache_miss++;
need_repaint = true;
command_cache[cache_index].command = cmd;
}
cache_index++;
}
if (need_repaint) {
m_context->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f));
}
}

void D2DPainter::begin() {
m_context->SetTarget(m_rt.Get());
m_context->BeginDraw();
m_context->Clear(D2D1::ColorF(D2D1::ColorF::Black, 0.0f));
}

void D2DPainter::end() {
Expand Down Expand Up @@ -228,8 +259,8 @@ void D2DPainter::ring(float centerX, float centerY, float outerRadius, float inn
m_context->FillGeometry(pathGeometry.Get(), m_brush.Get());
}

void D2DPainter::ring(
float centerX, float centerY, float outerRadius, float innerRadius, float startAngle, float sweepAngle, unsigned int color, bool clockwise) {
void D2DPainter::ring(float centerX, float centerY, float outerRadius, float innerRadius, float startAngle, float sweepAngle,
unsigned int color, bool clockwise) {
if (startAngle < 0) {
startAngle += 360.0f;
}
Expand Down Expand Up @@ -263,7 +294,7 @@ void D2DPainter::ring(
// outer arc start -> outer arc end
D2D1_POINT_2F outerEnd = D2D1::Point2F(centerX + outerRadius * std::cos(endRadians), centerY + outerRadius * std::sin(endRadians));
sink->AddArc(D2D1::ArcSegment(outerEnd, D2D1::SizeF(outerRadius, outerRadius), 0.0f, direction,
(sweepAngle > 180.0f) ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL));
(sweepAngle > 180.0f) ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL));

// outer arc end -> inner arc end
D2D1_POINT_2F innerEnd = D2D1::Point2F(centerX + innerRadius * std::cos(endRadians), centerY + innerRadius * std::sin(endRadians));
Expand All @@ -272,7 +303,7 @@ void D2DPainter::ring(
// inner arc end -> inner arc start
D2D1_POINT_2F innerStart = D2D1::Point2F(centerX + innerRadius * std::cos(startRadians), centerY + innerRadius * std::sin(startRadians));
sink->AddArc(D2D1::ArcSegment(innerStart, D2D1::SizeF(innerRadius, innerRadius), 0.0f, counterDirection,
(sweepAngle > 180.0f) ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL));
(sweepAngle > 180.0f) ? D2D1_ARC_SIZE_LARGE : D2D1_ARC_SIZE_SMALL));

// inner arc start -> outer arc start
sink->AddLine(outerStart);
Expand Down
Loading