Initial re-upload of spice2x-24-08-24
This commit is contained in:
70
overlay/imgui/extensions.cpp
Normal file
70
overlay/imgui/extensions.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "extensions.h"
|
||||
#include <cmath>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
void HelpMarker(const char* desc) {
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void WarnMarker(const char* desc, const char* warn) {
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
ImGui::TextDisabled("(!)");
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
||||
if (desc) {
|
||||
ImGui::TextUnformatted(desc);
|
||||
ImGui::TextUnformatted("");
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f));
|
||||
if (warn) {
|
||||
ImGui::TextUnformatted("WARNING:");
|
||||
ImGui::TextUnformatted(warn);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void DummyMarker() {
|
||||
// dummy marker that is the same width as HelpMarker/WarnMarker.
|
||||
ImGui::Dummy(ImVec2(22, 0));
|
||||
}
|
||||
|
||||
void Knob(float fraction, float size, float thickness, float pos_x, float pos_y) {
|
||||
|
||||
// get values
|
||||
auto radius = size * 0.5f;
|
||||
auto pos = ImGui::GetCursorScreenPos();
|
||||
if (pos_x >= 0) pos.x = pos_x;
|
||||
if (pos_y >= 0) pos.y = pos_y;
|
||||
auto center = ImVec2(pos.x + radius, pos.y + radius);
|
||||
auto draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
// dummy for spacing knob with other content
|
||||
if (pos_x < 0 && pos_y < 0) {
|
||||
ImGui::Dummy(ImVec2(size, size));
|
||||
}
|
||||
|
||||
// draw knob
|
||||
auto angle = (fraction + 0.25f) * (3.141592f * 2);
|
||||
draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32(ImGuiCol_FrameBg), 16);
|
||||
draw_list->AddLine(center,
|
||||
ImVec2(center.x + cosf(angle) * radius, center.y + sinf(angle) * radius),
|
||||
ImGui::GetColorU32(ImGuiCol_PlotHistogram),
|
||||
thickness);
|
||||
}
|
||||
}
|
||||
10
overlay/imgui/extensions.h
Normal file
10
overlay/imgui/extensions.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
void HelpMarker(const char* desc);
|
||||
void WarnMarker(const char* desc, const char* warn);
|
||||
void DummyMarker();
|
||||
void Knob(float fraction, float size, float thickness = 2.f,
|
||||
float pos_x = -1.f, float pos_y = -1.f);
|
||||
}
|
||||
360
overlay/imgui/impl_dx9.cpp
Normal file
360
overlay/imgui/impl_dx9.cpp
Normal file
@@ -0,0 +1,360 @@
|
||||
// dear imgui: Renderer for DirectX9
|
||||
// This needs to be used along with a Platform Binding (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
|
||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
||||
// https://github.com/ocornut/imgui
|
||||
|
||||
// CHANGELOG
|
||||
// (minor and older changes stripped away, please see git history for details)
|
||||
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
|
||||
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
|
||||
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
|
||||
// 2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288.
|
||||
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window.
|
||||
// 2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example.
|
||||
// 2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle.
|
||||
// 2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud.
|
||||
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
|
||||
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
|
||||
|
||||
#include "impl_dx9.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// DirectX
|
||||
#include <d3d9.h>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
// allow std::min use
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
// DirectX data
|
||||
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
|
||||
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
|
||||
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
|
||||
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
|
||||
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
|
||||
|
||||
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
|
||||
|
||||
// Render function.
|
||||
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop)
|
||||
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
|
||||
|
||||
// Avoid rendering when minimized
|
||||
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
|
||||
// Create and grow buffers if needed
|
||||
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) {
|
||||
if (g_pVB) {
|
||||
g_pVB->Release();
|
||||
g_pVB = NULL;
|
||||
}
|
||||
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
|
||||
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
|
||||
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
|
||||
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
|
||||
return;
|
||||
}
|
||||
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
|
||||
if (g_pIB) {
|
||||
g_pIB->Release();
|
||||
g_pIB = NULL;
|
||||
}
|
||||
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
|
||||
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
|
||||
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
|
||||
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
|
||||
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// Backup the DX9 state
|
||||
IDirect3DStateBlock9 *d3d9_state_block = NULL;
|
||||
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0)
|
||||
return;
|
||||
|
||||
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
|
||||
D3DMATRIX last_world, last_view, last_projection;
|
||||
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world);
|
||||
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view);
|
||||
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
|
||||
// Copy all vertices into a single contiguous buffer
|
||||
ImDrawVert *vtx_dst;
|
||||
ImDrawIdx *idx_dst;
|
||||
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
|
||||
D3DLOCK_DISCARD) < 0)
|
||||
return;
|
||||
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
|
||||
D3DLOCK_DISCARD) < 0)
|
||||
return;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++) {
|
||||
const ImDrawList *cmd_list = draw_data->CmdLists[n];
|
||||
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vtx_dst += cmd_list->VtxBuffer.Size;
|
||||
idx_dst += cmd_list->IdxBuffer.Size;
|
||||
}
|
||||
g_pVB->Unlock();
|
||||
g_pIB->Unlock();
|
||||
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
|
||||
g_pd3dDevice->SetIndices(g_pIB);
|
||||
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
|
||||
|
||||
// Setup viewport
|
||||
D3DVIEWPORT9 vp;
|
||||
vp.X = vp.Y = 0;
|
||||
vp.Width = (DWORD) draw_data->DisplaySize.x;
|
||||
vp.Height = (DWORD) draw_data->DisplaySize.y;
|
||||
vp.MinZ = 0.0f;
|
||||
vp.MaxZ = 1.0f;
|
||||
g_pd3dDevice->SetViewport(&vp);
|
||||
|
||||
g_pd3dDevice->SetPixelShader(nullptr);
|
||||
g_pd3dDevice->SetVertexShader(nullptr);
|
||||
|
||||
D3DCAPS9 caps {};
|
||||
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) {
|
||||
caps.NumSimultaneousRTs = 0UL;
|
||||
}
|
||||
|
||||
IDirect3DSurface9 *back_buffer = nullptr;
|
||||
IDirect3DSurface9 *depth_stencil = nullptr;
|
||||
IDirect3DSurface9 *render_targets[8];
|
||||
|
||||
// save all previous render target state
|
||||
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) {
|
||||
render_targets[target] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// get the previous depth stencil
|
||||
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) {
|
||||
depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// set the back buffer as the current render target
|
||||
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) {
|
||||
g_pd3dDevice->SetRenderTarget(0, back_buffer);
|
||||
g_pd3dDevice->SetDepthStencilSurface(nullptr);
|
||||
|
||||
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
g_pd3dDevice->SetRenderTarget(target, nullptr);
|
||||
}
|
||||
} else {
|
||||
back_buffer = nullptr;
|
||||
}
|
||||
|
||||
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient)
|
||||
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
|
||||
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
|
||||
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
|
||||
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
|
||||
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
|
||||
|
||||
// Setup orthographic projection matrix
|
||||
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
|
||||
// Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH()
|
||||
{
|
||||
float L = draw_data->DisplayPos.x + 0.5f;
|
||||
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
|
||||
float T = draw_data->DisplayPos.y + 0.5f;
|
||||
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
|
||||
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||
D3DMATRIX mat_projection =
|
||||
{{{
|
||||
2.0f / (R - L), 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 2.0f / (T - B), 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.5f, 0.0f,
|
||||
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f
|
||||
}}};
|
||||
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
|
||||
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
|
||||
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
|
||||
}
|
||||
|
||||
// Render command lists
|
||||
// (Because we merged all buffers into a single one, we maintain our own offset into them)
|
||||
int global_vtx_offset = 0;
|
||||
int global_idx_offset = 0;
|
||||
ImVec2 clip_off = draw_data->DisplayPos;
|
||||
for (int n = 0; n < draw_data->CmdListsCount; n++) {
|
||||
const ImDrawList *cmd_list = draw_data->CmdLists[n];
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
|
||||
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i];
|
||||
if (pcmd->UserCallback != NULL) {
|
||||
pcmd->UserCallback(cmd_list, pcmd);
|
||||
} else {
|
||||
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y),
|
||||
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)};
|
||||
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId);
|
||||
g_pd3dDevice->SetTexture(0, texture);
|
||||
g_pd3dDevice->SetScissorRect(&r);
|
||||
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
|
||||
pcmd->VtxOffset + global_vtx_offset, 0,
|
||||
(UINT) cmd_list->VtxBuffer.Size,
|
||||
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
|
||||
}
|
||||
}
|
||||
global_idx_offset += cmd_list->IdxBuffer.Size;
|
||||
global_vtx_offset += cmd_list->VtxBuffer.Size;
|
||||
}
|
||||
|
||||
if (back_buffer) {
|
||||
back_buffer->Release();
|
||||
back_buffer = nullptr;
|
||||
}
|
||||
|
||||
// restore previous depth stencil
|
||||
if (depth_stencil) {
|
||||
g_pd3dDevice->SetDepthStencilSurface(depth_stencil);
|
||||
depth_stencil->Release();
|
||||
depth_stencil = nullptr;
|
||||
}
|
||||
|
||||
// restore all render target state
|
||||
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) {
|
||||
auto render_target = render_targets[target];
|
||||
|
||||
if (render_target) {
|
||||
g_pd3dDevice->SetRenderTarget(target, render_target);
|
||||
render_target->Release();
|
||||
}
|
||||
}
|
||||
|
||||
// restore the DX9 transform
|
||||
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world);
|
||||
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view);
|
||||
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection);
|
||||
|
||||
// restore the DX9 state
|
||||
d3d9_state_block->Apply();
|
||||
d3d9_state_block->Release();
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) {
|
||||
|
||||
// Setup back-end capabilities flags
|
||||
auto &io = ImGui::GetIO();
|
||||
io.BackendRendererName = "imgui_impl_dx9";
|
||||
|
||||
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
|
||||
g_pd3dDevice = device;
|
||||
g_pd3dDevice->AddRef();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_Shutdown() {
|
||||
ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
|
||||
if (g_pd3dDevice) {
|
||||
g_pd3dDevice->Release();
|
||||
g_pd3dDevice = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool ImGui_ImplDX9_CreateFontsTexture() {
|
||||
// Build texture atlas
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
unsigned char *pixels;
|
||||
int width, height, bytes_per_pixel;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
|
||||
|
||||
// Upload texture to graphics system
|
||||
g_FontTexture = NULL;
|
||||
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8,
|
||||
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0)
|
||||
return false;
|
||||
D3DLOCKED_RECT tex_locked_rect;
|
||||
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK)
|
||||
return false;
|
||||
for (int y = 0; y < height; y++)
|
||||
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y,
|
||||
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel));
|
||||
g_FontTexture->UnlockRect(0);
|
||||
|
||||
// Store our identifier
|
||||
io.Fonts->TexID = (ImTextureID) g_FontTexture;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ImGui_ImplDX9_CreateDeviceObjects() {
|
||||
if (!g_pd3dDevice) {
|
||||
return false;
|
||||
}
|
||||
return ImGui_ImplDX9_CreateFontsTexture();
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_InvalidateDeviceObjects() {
|
||||
if (!g_pd3dDevice)
|
||||
return;
|
||||
if (g_pVB) {
|
||||
g_pVB->Release();
|
||||
g_pVB = NULL;
|
||||
}
|
||||
if (g_pIB) {
|
||||
g_pIB->Release();
|
||||
g_pIB = NULL;
|
||||
}
|
||||
if (g_FontTexture) {
|
||||
g_FontTexture->Release();
|
||||
g_FontTexture = NULL;
|
||||
ImGui::GetIO().Fonts->TexID = NULL;
|
||||
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
|
||||
}
|
||||
|
||||
void ImGui_ImplDX9_NewFrame() {
|
||||
if (!g_FontTexture) {
|
||||
ImGui_ImplDX9_CreateDeviceObjects();
|
||||
}
|
||||
|
||||
IDirect3DSwapChain9 *swap_chain = nullptr;
|
||||
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
D3DPRESENT_PARAMETERS present_params {};
|
||||
|
||||
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
|
||||
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
|
||||
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
|
||||
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
|
||||
} else {
|
||||
RECT rect {};
|
||||
GetClientRect(present_params.hDeviceWindow, &rect);
|
||||
|
||||
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
|
||||
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
|
||||
}
|
||||
}
|
||||
|
||||
swap_chain->Release();
|
||||
}
|
||||
}
|
||||
25
overlay/imgui/impl_dx9.h
Normal file
25
overlay/imgui/impl_dx9.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// dear imgui: Renderer for DirectX9
|
||||
// This needs to be used along with a Platform Binding (e.g. Win32)
|
||||
|
||||
// Implemented features:
|
||||
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp.
|
||||
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices.
|
||||
|
||||
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this.
|
||||
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp.
|
||||
// https://github.com/ocornut/imgui
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
struct IDirect3DDevice9;
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device);
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data);
|
||||
|
||||
// Use if you want to reset your rendering device without losing ImGui state.
|
||||
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
|
||||
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
|
||||
377
overlay/imgui/impl_spice.cpp
Normal file
377
overlay/imgui/impl_spice.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
#include "impl_spice.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "games/io.h"
|
||||
#include "launcher/launcher.h"
|
||||
#include "launcher/superexit.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "overlay/overlay.h"
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
// state
|
||||
static HWND g_hWnd = nullptr;
|
||||
static INT64 g_Time = 0;
|
||||
static INT64 g_TicksPerSecond = 0;
|
||||
static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT;
|
||||
|
||||
bool ImGui_ImplSpice_Init(HWND hWnd) {
|
||||
log_misc("imgui_impl_spice", "init");
|
||||
|
||||
// check if already initialized
|
||||
if (g_hWnd != nullptr) {
|
||||
if (g_hWnd == hWnd) {
|
||||
return true;
|
||||
} else {
|
||||
ImGui_ImplSpice_Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// init performance stuff
|
||||
if (!::QueryPerformanceFrequency((LARGE_INTEGER *)&g_TicksPerSecond))
|
||||
return false;
|
||||
if (!::QueryPerformanceCounter((LARGE_INTEGER *)&g_Time))
|
||||
return false;
|
||||
|
||||
// setup back-end capabilities flags
|
||||
g_hWnd = hWnd;
|
||||
ImGuiIO &io = ImGui::GetIO();
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
|
||||
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;
|
||||
io.BackendPlatformName = "imgui_impl_spice";
|
||||
|
||||
// keyboard mapping
|
||||
io.KeyMap[ImGuiKey_Tab] = VK_TAB;
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
|
||||
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
|
||||
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
|
||||
io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR;
|
||||
io.KeyMap[ImGuiKey_PageDown] = VK_NEXT;
|
||||
io.KeyMap[ImGuiKey_Home] = VK_HOME;
|
||||
io.KeyMap[ImGuiKey_End] = VK_END;
|
||||
io.KeyMap[ImGuiKey_Insert] = VK_INSERT;
|
||||
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
|
||||
io.KeyMap[ImGuiKey_Backspace] = VK_BACK;
|
||||
io.KeyMap[ImGuiKey_Space] = VK_SPACE;
|
||||
io.KeyMap[ImGuiKey_Enter] = VK_RETURN;
|
||||
io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE;
|
||||
io.KeyMap[ImGuiKey_KeyPadEnter] = VK_RETURN;
|
||||
io.KeyMap[ImGuiKey_A] = 'A';
|
||||
io.KeyMap[ImGuiKey_C] = 'C';
|
||||
io.KeyMap[ImGuiKey_V] = 'V';
|
||||
io.KeyMap[ImGuiKey_X] = 'X';
|
||||
io.KeyMap[ImGuiKey_Y] = 'Y';
|
||||
io.KeyMap[ImGuiKey_Z] = 'Z';
|
||||
|
||||
// get display size
|
||||
ImGui_ImplSpice_UpdateDisplaySize();
|
||||
|
||||
// return success
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_Shutdown() {
|
||||
log_misc("imgui_impl_spice", "shutdown");
|
||||
|
||||
// reset window handle
|
||||
g_hWnd = nullptr;
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_UpdateDisplaySize() {
|
||||
|
||||
// get display size
|
||||
RECT rect;
|
||||
::GetClientRect(g_hWnd, &rect);
|
||||
ImGui::GetIO().DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
|
||||
}
|
||||
|
||||
bool ImGui_ImplSpice_UpdateMouseCursor() {
|
||||
|
||||
// check if cursor should be changed
|
||||
auto &io = ImGui::GetIO();
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// update cursor
|
||||
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
|
||||
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) {
|
||||
|
||||
// hide OS mouse cursor if imgui is drawing it or if it wants no cursor
|
||||
::SetCursor(nullptr);
|
||||
|
||||
} else {
|
||||
|
||||
// show OS mouse cursor
|
||||
LPTSTR win32_cursor = IDC_ARROW;
|
||||
switch (imgui_cursor) {
|
||||
case ImGuiMouseCursor_Arrow:
|
||||
win32_cursor = IDC_ARROW;
|
||||
break;
|
||||
case ImGuiMouseCursor_TextInput:
|
||||
win32_cursor = IDC_IBEAM;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeAll:
|
||||
win32_cursor = IDC_SIZEALL;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeEW:
|
||||
win32_cursor = IDC_SIZEWE;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNS:
|
||||
win32_cursor = IDC_SIZENS;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNESW:
|
||||
win32_cursor = IDC_SIZENESW;
|
||||
break;
|
||||
case ImGuiMouseCursor_ResizeNWSE:
|
||||
win32_cursor = IDC_SIZENWSE;
|
||||
break;
|
||||
case ImGuiMouseCursor_Hand:
|
||||
win32_cursor = IDC_HAND;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
::SetCursor(::LoadCursor(nullptr, win32_cursor));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ImGui_ImplSpice_UpdateMousePos() {
|
||||
|
||||
// get current window size
|
||||
RECT rect;
|
||||
if (GetClientRect(g_hWnd, &rect)) {
|
||||
ImVec2 window_size(
|
||||
(float)(rect.right - rect.left),
|
||||
(float)(rect.bottom - rect.top));
|
||||
|
||||
// set OS mouse position if requested
|
||||
auto &io = ImGui::GetIO();
|
||||
if (io.WantSetMousePos) {
|
||||
POINT pos {
|
||||
.x = static_cast<long>(io.MousePos.x),
|
||||
.y = static_cast<long>(io.MousePos.y),
|
||||
};
|
||||
::ClientToScreen(g_hWnd, &pos);
|
||||
::SetCursorPos(
|
||||
static_cast<int>(pos.x / io.DisplaySize.x * window_size.x),
|
||||
static_cast<int>(pos.y / io.DisplaySize.y * window_size.y));
|
||||
}
|
||||
|
||||
// set mouse position
|
||||
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
||||
POINT pos;
|
||||
if (HWND active_window = ::GetForegroundWindow()) {
|
||||
if (active_window == g_hWnd
|
||||
|| ::IsChild(active_window, g_hWnd)
|
||||
|| ::IsChild(g_hWnd, active_window)
|
||||
|| active_window == SPICETOUCH_TOUCH_HWND) {
|
||||
if (::GetCursorPos(&pos) && ::ScreenToClient(g_hWnd, &pos)) {
|
||||
io.MousePos = ImVec2(
|
||||
(float) pos.x * io.DisplaySize.x / window_size.x,
|
||||
(float) pos.y * io.DisplaySize.y / window_size.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to touch hwnd
|
||||
if (io.MousePos.x == -FLT_MAX || io.MousePos.y == -FLT_MAX) {
|
||||
if (SPICETOUCH_TOUCH_HWND) {
|
||||
if (::GetCursorPos(&pos) && ::ScreenToClient(SPICETOUCH_TOUCH_HWND, &pos)) {
|
||||
io.MousePos = ImVec2(
|
||||
(float) pos.x * io.DisplaySize.x / window_size.x,
|
||||
(float) pos.y * io.DisplaySize.y / window_size.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alternatively check touch
|
||||
std::vector<TouchPoint> touch_points;
|
||||
touch_get_points(touch_points, true);
|
||||
static size_t delay_touch = 0;
|
||||
static size_t delay_touch_target = 2;
|
||||
static DWORD last_touch_id = ~0u;
|
||||
if (!touch_points.empty()) {
|
||||
|
||||
// use the first touch point
|
||||
auto &tp = touch_points[0];
|
||||
io.MousePos.x = tp.x * io.DisplaySize.x / window_size.x;
|
||||
io.MousePos.y = tp.y * io.DisplaySize.y / window_size.y;
|
||||
|
||||
// update cursor position
|
||||
if (!tp.mouse) {
|
||||
pos.x = static_cast<long>(io.MousePos.x);
|
||||
pos.y = static_cast<long>(io.MousePos.y);
|
||||
::ClientToScreen(g_hWnd, &pos);
|
||||
::SetCursorPos(
|
||||
static_cast<long>(pos.x / io.DisplaySize.x * window_size.x),
|
||||
static_cast<long>(pos.y / io.DisplaySize.y * window_size.y));
|
||||
}
|
||||
|
||||
// delay press
|
||||
io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id;
|
||||
if (last_touch_id == ~0u) {
|
||||
last_touch_id = tp.id;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// reset
|
||||
delay_touch = 0;
|
||||
last_touch_id = ~0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui_ImplSpice_NewFrame() {
|
||||
|
||||
// check if font is built
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
IM_ASSERT(io.Fonts->IsBuilt());
|
||||
|
||||
// setup time step
|
||||
INT64 current_time;
|
||||
::QueryPerformanceCounter((LARGE_INTEGER *)¤t_time);
|
||||
io.DeltaTime = (float) (current_time - g_Time) / g_TicksPerSecond;
|
||||
g_Time = current_time;
|
||||
|
||||
// remember old state
|
||||
BYTE KeysDownOld[sizeof(io.KeysDown)];
|
||||
for (size_t i = 0; i < sizeof(io.KeysDown); i++) {
|
||||
KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0;
|
||||
}
|
||||
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT];
|
||||
KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT];
|
||||
|
||||
// reset keys state
|
||||
io.MouseWheel = 0;
|
||||
io.KeyCtrl = false;
|
||||
io.KeyShift = false;
|
||||
io.KeyAlt = false;
|
||||
io.KeySuper = false;
|
||||
memset(io.KeysDown, false, sizeof(io.KeysDown));
|
||||
memset(io.MouseDown, false, sizeof(io.MouseDown));
|
||||
|
||||
// early quit if window not in focus
|
||||
if (!superexit::has_focus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// read keyboard modifiers inputs
|
||||
io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0;
|
||||
io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||
io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0;
|
||||
io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0;
|
||||
io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0;
|
||||
|
||||
// apply windows mouse buttons
|
||||
io.MouseDown[0] |= (GetAsyncKeyState(VK_LBUTTON)) != 0;
|
||||
io.MouseDown[1] |= (GetAsyncKeyState(VK_RBUTTON)) != 0;
|
||||
io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0;
|
||||
|
||||
// read new keys state
|
||||
static long mouse_wheel_last = 0;
|
||||
long mouse_wheel = 0;
|
||||
if (RI_MGR != nullptr) {
|
||||
auto devices = RI_MGR->devices_get();
|
||||
for (auto &device : devices) {
|
||||
switch (device.type) {
|
||||
case rawinput::MOUSE: {
|
||||
auto &mouse = device.mouseInfo;
|
||||
|
||||
// mouse button triggers
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) {
|
||||
io.MouseDown[0] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) {
|
||||
io.MouseDown[1] = true;
|
||||
}
|
||||
if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) {
|
||||
io.MouseDown[2] = true;
|
||||
}
|
||||
|
||||
// final mouse wheel value should be all devices combined
|
||||
mouse_wheel += mouse->pos_wheel;
|
||||
|
||||
break;
|
||||
}
|
||||
case rawinput::KEYBOARD: {
|
||||
|
||||
// iterate all virtual key codes
|
||||
for (size_t vKey = 0; vKey < 256; vKey++) {
|
||||
|
||||
// get state (combined from all pages)
|
||||
auto &key_states = device.keyboardInfo->key_states;
|
||||
bool state = false;
|
||||
for (size_t page_index = 0; page_index < 1024; page_index += 256) {
|
||||
state |= key_states[page_index + vKey];
|
||||
}
|
||||
|
||||
// trigger
|
||||
io.KeysDown[vKey] |= state;
|
||||
|
||||
// generate character input, but only if WM_CHAR didn't take over the
|
||||
// functionality
|
||||
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
|
||||
UCHAR buf[2];
|
||||
auto ret = ToAscii(
|
||||
static_cast<UINT>(vKey),
|
||||
0,
|
||||
static_cast<const BYTE *>(KeysDownOld),
|
||||
reinterpret_cast<LPWORD>(buf),
|
||||
0);
|
||||
if (ret > 0) {
|
||||
for (int i = 0; i < ret; i++) {
|
||||
overlay::OVERLAY->input_char(buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// navigator input
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
if (buttons && (!overlay::OVERLAY || overlay::OVERLAY->hotkeys_triggered())) {
|
||||
struct {
|
||||
size_t index;
|
||||
Button &btn;
|
||||
} NAV_MAPPING[] = {
|
||||
{ ImGuiNavInput_Activate, buttons->at(games::OverlayButtons::NavigatorActivate )},
|
||||
{ ImGuiNavInput_Cancel, buttons->at(games::OverlayButtons::NavigatorCancel) },
|
||||
{ ImGuiNavInput_DpadUp, buttons->at(games::OverlayButtons::NavigatorUp) },
|
||||
{ ImGuiNavInput_DpadDown, buttons->at(games::OverlayButtons::NavigatorDown) },
|
||||
{ ImGuiNavInput_DpadLeft, buttons->at(games::OverlayButtons::NavigatorLeft) },
|
||||
{ ImGuiNavInput_DpadRight, buttons->at(games::OverlayButtons::NavigatorRight) },
|
||||
};
|
||||
for (auto mapping : NAV_MAPPING) {
|
||||
if (GameAPI::Buttons::getState(RI_MGR, mapping.btn)) {
|
||||
io.NavInputs[mapping.index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set mouse wheel
|
||||
auto mouse_diff = mouse_wheel - mouse_wheel_last;
|
||||
mouse_wheel_last = mouse_wheel;
|
||||
io.MouseWheel = mouse_diff;
|
||||
|
||||
// update OS mouse position
|
||||
ImGui_ImplSpice_UpdateMousePos();
|
||||
|
||||
// update OS mouse cursor with the cursor requested by imgui
|
||||
ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor();
|
||||
if (g_LastMouseCursor != mouse_cursor) {
|
||||
g_LastMouseCursor = mouse_cursor;
|
||||
ImGui_ImplSpice_UpdateMouseCursor();
|
||||
}
|
||||
}
|
||||
10
overlay/imgui/impl_spice.h
Normal file
10
overlay/imgui/impl_spice.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
IMGUI_IMPL_API bool ImGui_ImplSpice_Init(HWND hWnd);
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_Shutdown();
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_UpdateDisplaySize();
|
||||
IMGUI_IMPL_API bool ImGui_ImplSpice_UpdateMouseCursor();
|
||||
IMGUI_IMPL_API void ImGui_ImplSpice_NewFrame();
|
||||
731
overlay/imgui/impl_sw.cpp
Normal file
731
overlay/imgui/impl_sw.cpp
Normal file
@@ -0,0 +1,731 @@
|
||||
// Original File By Emil Ernerfeldt 2018
|
||||
// https://github.com/emilk/imgui_software_renderer
|
||||
// LICENSE:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
#include "impl_sw.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "external/imgui/imgui.h"
|
||||
|
||||
namespace imgui_sw {
|
||||
namespace {
|
||||
|
||||
struct Texture
|
||||
{
|
||||
const uint8_t* pixels; // 8-bit.
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct PaintTarget
|
||||
{
|
||||
uint32_t* pixels;
|
||||
int width;
|
||||
int height;
|
||||
ImVec2 scale; // Multiply ImGui (point) coordinates with this to get pixel coordinates.
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct ColorInt
|
||||
{
|
||||
uint32_t a, b, g, r;
|
||||
|
||||
ColorInt() = default;
|
||||
|
||||
explicit ColorInt(uint32_t x)
|
||||
{
|
||||
a = (x >> IM_COL32_A_SHIFT) & 0xFFu;
|
||||
b = (x >> IM_COL32_B_SHIFT) & 0xFFu;
|
||||
g = (x >> IM_COL32_G_SHIFT) & 0xFFu;
|
||||
r = (x >> IM_COL32_R_SHIFT) & 0xFFu;
|
||||
}
|
||||
|
||||
uint32_t toUint32() const
|
||||
{
|
||||
#ifdef IMGUI_USE_BGRA_PACKED_COLOR
|
||||
return (a << 24u) | (r << 16u) | (g << 8u) | b;
|
||||
#else
|
||||
return (a << 24u) | (b << 16u) | (g << 8u) | r;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
ColorInt blend(ColorInt target, ColorInt source)
|
||||
{
|
||||
ColorInt result;
|
||||
result.a = 0; // Whatever.
|
||||
result.b = (source.b * source.a + target.b * (255 - source.a)) / 255;
|
||||
result.g = (source.g * source.a + target.g * (255 - source.a)) / 255;
|
||||
result.r = (source.r * source.a + target.r * (255 - source.a)) / 255;
|
||||
return result;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Used for interpolating vertex attributes (color and texture coordinates) in a triangle.
|
||||
|
||||
struct Barycentric
|
||||
{
|
||||
float w0, w1, w2;
|
||||
};
|
||||
|
||||
Barycentric operator*(const float f, const Barycentric& va)
|
||||
{
|
||||
return { f * va.w0, f * va.w1, f * va.w2 };
|
||||
}
|
||||
|
||||
void operator+=(Barycentric& a, const Barycentric& b)
|
||||
{
|
||||
a.w0 += b.w0;
|
||||
a.w1 += b.w1;
|
||||
a.w2 += b.w2;
|
||||
}
|
||||
|
||||
Barycentric operator+(const Barycentric& a, const Barycentric& b)
|
||||
{
|
||||
return Barycentric{ a.w0 + b.w0, a.w1 + b.w1, a.w2 + b.w2 };
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Useful operators on ImGui vectors:
|
||||
|
||||
ImVec2 operator*(const float f, const ImVec2& v)
|
||||
{
|
||||
return ImVec2{f * v.x, f * v.y};
|
||||
}
|
||||
|
||||
ImVec2 operator+(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return ImVec2{a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
ImVec2 operator-(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return ImVec2{a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
bool operator!=(const ImVec2& a, const ImVec2& b)
|
||||
{
|
||||
return a.x != b.x || a.y != b.y;
|
||||
}
|
||||
|
||||
ImVec4 operator*(const float f, const ImVec4& v)
|
||||
{
|
||||
return ImVec4{f * v.x, f * v.y, f * v.z, f * v.w};
|
||||
}
|
||||
|
||||
ImVec4 operator+(const ImVec4& a, const ImVec4& b)
|
||||
{
|
||||
return ImVec4{a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copies of functions in ImGui, inlined for speed:
|
||||
|
||||
ImVec4 color_convert_u32_to_float4(ImU32 in)
|
||||
{
|
||||
const float s = 1.0f / 255.0f;
|
||||
return ImVec4(
|
||||
((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
|
||||
((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
|
||||
}
|
||||
|
||||
ImU32 color_convert_float4_to_u32(const ImVec4& in)
|
||||
{
|
||||
ImU32 out;
|
||||
out = uint32_t(in.x * 255.0f + 0.5f) << IM_COL32_R_SHIFT;
|
||||
out |= uint32_t(in.y * 255.0f + 0.5f) << IM_COL32_G_SHIFT;
|
||||
out |= uint32_t(in.z * 255.0f + 0.5f) << IM_COL32_B_SHIFT;
|
||||
out |= uint32_t(in.w * 255.0f + 0.5f) << IM_COL32_A_SHIFT;
|
||||
return out;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// For fast and subpixel-perfect triangle rendering we used fixed point arithmetic.
|
||||
// To keep the code simple we use 64 bits to avoid overflows.
|
||||
|
||||
using Int = int64_t;
|
||||
const Int kFixedBias = 256;
|
||||
|
||||
struct Point
|
||||
{
|
||||
Int x, y;
|
||||
};
|
||||
|
||||
Int orient2d(const Point& a, const Point& b, const Point& c)
|
||||
{
|
||||
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
||||
}
|
||||
|
||||
Int as_int(float v)
|
||||
{
|
||||
return static_cast<Int>(std::floor(v * kFixedBias));
|
||||
}
|
||||
|
||||
Point as_point(ImVec2 v)
|
||||
{
|
||||
return Point{as_int(v.x), as_int(v.y)};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
float min3(float a, float b, float c)
|
||||
{
|
||||
if (a < b && a < c) { return a; }
|
||||
return b < c ? b : c;
|
||||
}
|
||||
|
||||
float max3(float a, float b, float c)
|
||||
{
|
||||
if (a > b && a > c) { return a; }
|
||||
return b > c ? b : c;
|
||||
}
|
||||
|
||||
float barycentric(const ImVec2& a, const ImVec2& b, const ImVec2& point)
|
||||
{
|
||||
return (b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x);
|
||||
}
|
||||
|
||||
inline uint8_t sample_texture(const Texture& texture, const ImVec2& uv)
|
||||
{
|
||||
int tx = static_cast<int>(uv.x * (texture.width - 1.0f) + 0.5f);
|
||||
int ty = static_cast<int>(uv.y * (texture.height - 1.0f) + 0.5f);
|
||||
|
||||
// Clamp to inside of texture:
|
||||
tx = std::max(tx, 0);
|
||||
tx = std::min(tx, texture.width - 1);
|
||||
ty = std::max(ty, 0);
|
||||
ty = std::min(ty, texture.height - 1);
|
||||
|
||||
return texture.pixels[ty * texture.width + tx];
|
||||
}
|
||||
|
||||
void paint_uniform_rectangle(
|
||||
const PaintTarget& target,
|
||||
const ImVec2& min_f,
|
||||
const ImVec2& max_f,
|
||||
const ColorInt& color,
|
||||
Stats* stats)
|
||||
{
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(target.scale.x * min_f.x + 0.5f);
|
||||
int min_y_i = static_cast<int>(target.scale.y * min_f.y + 0.5f);
|
||||
int max_x_i = static_cast<int>(target.scale.x * max_f.x + 0.5f);
|
||||
int max_y_i = static_cast<int>(target.scale.y * max_f.y + 0.5f);
|
||||
|
||||
// Clamp to render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
stats->uniform_rectangle_pixels += (max_x_i - min_x_i) * (max_y_i - min_y_i);
|
||||
|
||||
// We often blend the same colors over and over again, so optimize for this (saves 25% total cpu):
|
||||
uint32_t last_target_pixel = target.pixels[min_y_i * target.width + min_x_i];
|
||||
uint32_t last_output = blend(ColorInt(last_target_pixel), color).toUint32();
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y) {
|
||||
for (int x = min_x_i; x < max_x_i; ++x) {
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
if (target_pixel == last_target_pixel) {
|
||||
target_pixel = last_output;
|
||||
continue;
|
||||
}
|
||||
last_target_pixel = target_pixel;
|
||||
target_pixel = blend(ColorInt(target_pixel), color).toUint32();
|
||||
last_output = target_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void paint_uniform_textured_rectangle(
|
||||
const PaintTarget& target,
|
||||
const Texture& texture,
|
||||
const ImVec4& clip_rect,
|
||||
const ImDrawVert& min_v,
|
||||
const ImDrawVert& max_v,
|
||||
Stats* stats)
|
||||
{
|
||||
const ImVec2 min_p = ImVec2(target.scale.x * min_v.pos.x, target.scale.y * min_v.pos.y);
|
||||
const ImVec2 max_p = ImVec2(target.scale.x * max_v.pos.x, target.scale.y * max_v.pos.y);
|
||||
|
||||
// Find bounding box:
|
||||
float min_x_f = min_p.x;
|
||||
float min_y_f = min_p.y;
|
||||
float max_x_f = max_p.x;
|
||||
float max_y_f = max_p.y;
|
||||
|
||||
// Clip against clip_rect:
|
||||
min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x);
|
||||
min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y);
|
||||
max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f);
|
||||
max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f);
|
||||
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(min_x_f);
|
||||
int min_y_i = static_cast<int>(min_y_f);
|
||||
int max_x_i = static_cast<int>(max_x_f + 1.0f);
|
||||
int max_y_i = static_cast<int>(max_y_f + 1.0f);
|
||||
|
||||
// Clip against render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
stats->font_pixels += (max_x_i - min_x_i) * (max_y_i - min_y_i);
|
||||
|
||||
const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x,
|
||||
min_y_i + 0.5f * target.scale.y);
|
||||
|
||||
const ImVec2 delta_uv_per_pixel = {
|
||||
(max_v.uv.x - min_v.uv.x) / (max_p.x - min_p.x),
|
||||
(max_v.uv.y - min_v.uv.y) / (max_p.y - min_p.y),
|
||||
};
|
||||
const ImVec2 uv_topleft = {
|
||||
min_v.uv.x + (topleft.x - min_v.pos.x) * delta_uv_per_pixel.x,
|
||||
min_v.uv.y + (topleft.y - min_v.pos.y) * delta_uv_per_pixel.y,
|
||||
};
|
||||
ImVec2 current_uv = uv_topleft;
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y, current_uv.y += delta_uv_per_pixel.y) {
|
||||
current_uv.x = uv_topleft.x;
|
||||
for (int x = min_x_i; x < max_x_i; ++x, current_uv.x += delta_uv_per_pixel.x) {
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
const uint8_t texel = sample_texture(texture, current_uv);
|
||||
|
||||
// The font texture is all black or all white, so optimize for this:
|
||||
if (texel == 0) { continue; }
|
||||
|
||||
// Other textured rectangles
|
||||
ColorInt source_color = ColorInt(min_v.col);
|
||||
source_color.a = source_color.a * texel / 255;
|
||||
target_pixel = blend(ColorInt(target_pixel), source_color).toUint32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When two triangles share an edge, we want to draw the pixels on that edge exactly once.
|
||||
// The edge will be the same, but the direction will be the opposite
|
||||
// (assuming the two triangles have the same winding order).
|
||||
// Which edge wins? This functions decides.
|
||||
bool is_dominant_edge(ImVec2 edge)
|
||||
{
|
||||
// return edge.x < 0 || (edge.x == 0 && edge.y > 0);
|
||||
return edge.y > 0 || (edge.y == 0 && edge.x < 0);
|
||||
}
|
||||
|
||||
// Handles triangles in any winding order (CW/CCW)
|
||||
void paint_triangle(
|
||||
const PaintTarget& target,
|
||||
const Texture* texture,
|
||||
const ImVec4& clip_rect,
|
||||
const ImDrawVert& v0,
|
||||
const ImDrawVert& v1,
|
||||
const ImDrawVert& v2,
|
||||
Stats* stats)
|
||||
{
|
||||
const ImVec2 p0 = ImVec2(target.scale.x * v0.pos.x, target.scale.y * v0.pos.y);
|
||||
const ImVec2 p1 = ImVec2(target.scale.x * v1.pos.x, target.scale.y * v1.pos.y);
|
||||
const ImVec2 p2 = ImVec2(target.scale.x * v2.pos.x, target.scale.y * v2.pos.y);
|
||||
|
||||
const auto rect_area = barycentric(p0, p1, p2); // Can be positive or negative depending on winding order
|
||||
if (rect_area == 0.0f) { return; }
|
||||
// if (rect_area < 0.0f) { return paint_triangle(target, texture, clip_rect, v0, v2, v1, stats); }
|
||||
|
||||
// Find bounding box:
|
||||
float min_x_f = min3(p0.x, p1.x, p2.x);
|
||||
float min_y_f = min3(p0.y, p1.y, p2.y);
|
||||
float max_x_f = max3(p0.x, p1.x, p2.x);
|
||||
float max_y_f = max3(p0.y, p1.y, p2.y);
|
||||
|
||||
// Clip against clip_rect:
|
||||
min_x_f = std::max(min_x_f, target.scale.x * clip_rect.x);
|
||||
min_y_f = std::max(min_y_f, target.scale.y * clip_rect.y);
|
||||
max_x_f = std::min(max_x_f, target.scale.x * clip_rect.z - 0.5f);
|
||||
max_y_f = std::min(max_y_f, target.scale.y * clip_rect.w - 0.5f);
|
||||
|
||||
// Integer bounding box [min, max):
|
||||
int min_x_i = static_cast<int>(min_x_f);
|
||||
int min_y_i = static_cast<int>(min_y_f);
|
||||
int max_x_i = static_cast<int>(max_x_f + 1.0f);
|
||||
int max_y_i = static_cast<int>(max_y_f + 1.0f);
|
||||
|
||||
// Clip against render target:
|
||||
min_x_i = std::max(min_x_i, 0);
|
||||
min_y_i = std::max(min_y_i, 0);
|
||||
max_x_i = std::min(max_x_i, target.width);
|
||||
max_y_i = std::min(max_y_i, target.height);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Set up interpolation of barycentric coordinates:
|
||||
|
||||
const auto topleft = ImVec2(min_x_i + 0.5f * target.scale.x,
|
||||
min_y_i + 0.5f * target.scale.y);
|
||||
const auto dx = ImVec2(1, 0);
|
||||
const auto dy = ImVec2(0, 1);
|
||||
|
||||
const auto w0_topleft = barycentric(p1, p2, topleft);
|
||||
const auto w1_topleft = barycentric(p2, p0, topleft);
|
||||
const auto w2_topleft = barycentric(p0, p1, topleft);
|
||||
|
||||
const auto w0_dx = barycentric(p1, p2, topleft + dx) - w0_topleft;
|
||||
const auto w1_dx = barycentric(p2, p0, topleft + dx) - w1_topleft;
|
||||
const auto w2_dx = barycentric(p0, p1, topleft + dx) - w2_topleft;
|
||||
|
||||
const auto w0_dy = barycentric(p1, p2, topleft + dy) - w0_topleft;
|
||||
const auto w1_dy = barycentric(p2, p0, topleft + dy) - w1_topleft;
|
||||
const auto w2_dy = barycentric(p0, p1, topleft + dy) - w2_topleft;
|
||||
|
||||
const Barycentric bary_0 { 1, 0, 0 };
|
||||
const Barycentric bary_1 { 0, 1, 0 };
|
||||
const Barycentric bary_2 { 0, 0, 1 };
|
||||
|
||||
const auto inv_area = 1 / rect_area;
|
||||
const Barycentric bary_topleft = inv_area * (w0_topleft * bary_0 + w1_topleft * bary_1 + w2_topleft * bary_2);
|
||||
const Barycentric bary_dx = inv_area * (w0_dx * bary_0 + w1_dx * bary_1 + w2_dx * bary_2);
|
||||
const Barycentric bary_dy = inv_area * (w0_dy * bary_0 + w1_dy * bary_1 + w2_dy * bary_2);
|
||||
|
||||
Barycentric bary_current_row = bary_topleft;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// For pixel-perfect inside/outside testing:
|
||||
|
||||
const int sign = rect_area > 0 ? 1 : -1; // winding order?
|
||||
|
||||
const int bias0i = is_dominant_edge(p2 - p1) ? 0 : -1;
|
||||
const int bias1i = is_dominant_edge(p0 - p2) ? 0 : -1;
|
||||
const int bias2i = is_dominant_edge(p1 - p0) ? 0 : -1;
|
||||
|
||||
const auto p0i = as_point(p0);
|
||||
const auto p1i = as_point(p1);
|
||||
const auto p2i = as_point(p2);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
const bool has_uniform_color = (v0.col == v1.col && v0.col == v2.col);
|
||||
|
||||
const ImVec4 c0 = color_convert_u32_to_float4(v0.col);
|
||||
const ImVec4 c1 = color_convert_u32_to_float4(v1.col);
|
||||
const ImVec4 c2 = color_convert_u32_to_float4(v2.col);
|
||||
|
||||
// We often blend the same colors over and over again, so optimize for this (saves 10% total cpu):
|
||||
uint32_t last_target_pixel = 0;
|
||||
uint32_t last_output = blend(ColorInt(last_target_pixel), ColorInt(v0.col)).toUint32();
|
||||
|
||||
for (int y = min_y_i; y < max_y_i; ++y) {
|
||||
auto bary = bary_current_row;
|
||||
|
||||
bool has_been_inside_this_row = false;
|
||||
|
||||
for (int x = min_x_i; x < max_x_i; ++x) {
|
||||
const auto w0 = bary.w0;
|
||||
const auto w1 = bary.w1;
|
||||
const auto w2 = bary.w2;
|
||||
bary += bary_dx;
|
||||
|
||||
{
|
||||
// Inside/outside test:
|
||||
const auto p = Point{kFixedBias * x + kFixedBias / 2, kFixedBias * y + kFixedBias / 2};
|
||||
const auto w0i = sign * orient2d(p1i, p2i, p) + bias0i;
|
||||
const auto w1i = sign * orient2d(p2i, p0i, p) + bias1i;
|
||||
const auto w2i = sign * orient2d(p0i, p1i, p) + bias2i;
|
||||
if (w0i < 0 || w1i < 0 || w2i < 0) {
|
||||
if (has_been_inside_this_row) {
|
||||
break; // Gives a nice 10% speedup
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
has_been_inside_this_row = true;
|
||||
|
||||
uint32_t& target_pixel = target.pixels[y * target.width + x];
|
||||
|
||||
if (has_uniform_color && !texture) {
|
||||
stats->uniform_triangle_pixels += 1;
|
||||
if (target_pixel == last_target_pixel) {
|
||||
target_pixel = last_output;
|
||||
continue;
|
||||
}
|
||||
last_target_pixel = target_pixel;
|
||||
target_pixel = blend(ColorInt(target_pixel), ColorInt(v0.col)).toUint32();
|
||||
last_output = target_pixel;
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec4 src_color;
|
||||
|
||||
if (has_uniform_color) {
|
||||
src_color = c0;
|
||||
} else {
|
||||
stats->gradient_triangle_pixels += 1;
|
||||
src_color = w0 * c0 + w1 * c1 + w2 * c2;
|
||||
}
|
||||
|
||||
if (texture) {
|
||||
stats->textured_triangle_pixels += 1;
|
||||
const ImVec2 uv = w0 * v0.uv + w1 * v1.uv + w2 * v2.uv;
|
||||
src_color.w *= sample_texture(*texture, uv) / 255.0f;
|
||||
}
|
||||
|
||||
if (src_color.w <= 0.0f) { continue; } // Transparent.
|
||||
if (src_color.w >= 1.0f) {
|
||||
// Opaque, no blending needed:
|
||||
target_pixel = color_convert_float4_to_u32(src_color);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec4 target_color = color_convert_u32_to_float4(target_pixel);
|
||||
const auto blended_color = src_color.w * src_color + (1.0f - src_color.w) * target_color;
|
||||
target_pixel = color_convert_float4_to_u32(blended_color);
|
||||
}
|
||||
|
||||
bary_current_row += bary_dy;
|
||||
}
|
||||
}
|
||||
|
||||
void paint_draw_cmd(
|
||||
const PaintTarget& target,
|
||||
const ImDrawVert* vertices,
|
||||
const ImDrawIdx* idx_buffer,
|
||||
const ImDrawCmd& pcmd,
|
||||
const SwOptions& options,
|
||||
Stats* stats)
|
||||
{
|
||||
const auto texture = reinterpret_cast<const Texture*>(pcmd.TextureId);
|
||||
const auto offset = pcmd.IdxOffset;
|
||||
|
||||
// ImGui uses the first pixel for "white".
|
||||
const ImVec2 white_uv = ImVec2(0.5f / texture->width, 0.5f / texture->height);
|
||||
|
||||
for (size_t i = 0; i + 3 <= pcmd.ElemCount; ) {
|
||||
const auto io = i + offset;
|
||||
const ImDrawVert& v0 = vertices[idx_buffer[io + 0]];
|
||||
const ImDrawVert& v1 = vertices[idx_buffer[io + 1]];
|
||||
const ImDrawVert& v2 = vertices[idx_buffer[io + 2]];
|
||||
|
||||
// Text is common, and is made of textured rectangles. So let's optimize for it.
|
||||
// This assumes the ImGui way to layout text does not change.
|
||||
if (options.optimize_text && i + 6 <= pcmd.ElemCount &&
|
||||
idx_buffer[io + 3] == idx_buffer[io + 0] && idx_buffer[io + 4] == idx_buffer[io + 2]) {
|
||||
const ImDrawVert& v3 = vertices[idx_buffer[io + 5]];
|
||||
|
||||
if (v0.pos.x == v3.pos.x &&
|
||||
v1.pos.x == v2.pos.x &&
|
||||
v0.pos.y == v1.pos.y &&
|
||||
v2.pos.y == v3.pos.y &&
|
||||
v0.uv.x == v3.uv.x &&
|
||||
v1.uv.x == v2.uv.x &&
|
||||
v0.uv.y == v1.uv.y &&
|
||||
v2.uv.y == v3.uv.y)
|
||||
{
|
||||
const bool has_uniform_color =
|
||||
v0.col == v1.col &&
|
||||
v0.col == v2.col &&
|
||||
v0.col == v3.col;
|
||||
|
||||
const bool has_texture =
|
||||
v0.uv != white_uv ||
|
||||
v1.uv != white_uv ||
|
||||
v2.uv != white_uv ||
|
||||
v3.uv != white_uv;
|
||||
|
||||
if (has_uniform_color && has_texture)
|
||||
{
|
||||
paint_uniform_textured_rectangle(target, *texture, pcmd.ClipRect, v0, v2, stats);
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A lot of the big stuff are uniformly colored rectangles,
|
||||
// so we can save a lot of CPU by detecting them:
|
||||
if (options.optimize_rectangles && i + 6 <= pcmd.ElemCount) {
|
||||
const ImDrawVert& v3 = vertices[idx_buffer[io + 3]];
|
||||
const ImDrawVert& v4 = vertices[idx_buffer[io + 4]];
|
||||
const ImDrawVert& v5 = vertices[idx_buffer[io + 5]];
|
||||
|
||||
ImVec2 min, max;
|
||||
min.x = min3(v0.pos.x, v1.pos.x, v2.pos.x);
|
||||
min.y = min3(v0.pos.y, v1.pos.y, v2.pos.y);
|
||||
max.x = max3(v0.pos.x, v1.pos.x, v2.pos.x);
|
||||
max.y = max3(v0.pos.y, v1.pos.y, v2.pos.y);
|
||||
|
||||
// Not the prettiest way to do this, but it catches all cases
|
||||
// of a rectangle split into two triangles.
|
||||
// TODO: Stop it from also assuming duplicate triangles is one rectangle.
|
||||
if ((v0.pos.x == min.x || v0.pos.x == max.x) &&
|
||||
(v0.pos.y == min.y || v0.pos.y == max.y) &&
|
||||
(v1.pos.x == min.x || v1.pos.x == max.x) &&
|
||||
(v1.pos.y == min.y || v1.pos.y == max.y) &&
|
||||
(v2.pos.x == min.x || v2.pos.x == max.x) &&
|
||||
(v2.pos.y == min.y || v2.pos.y == max.y) &&
|
||||
(v3.pos.x == min.x || v3.pos.x == max.x) &&
|
||||
(v3.pos.y == min.y || v3.pos.y == max.y) &&
|
||||
(v4.pos.x == min.x || v4.pos.x == max.x) &&
|
||||
(v4.pos.y == min.y || v4.pos.y == max.y) &&
|
||||
(v5.pos.x == min.x || v5.pos.x == max.x) &&
|
||||
(v5.pos.y == min.y || v5.pos.y == max.y))
|
||||
{
|
||||
const bool has_uniform_color =
|
||||
v0.col == v1.col &&
|
||||
v0.col == v2.col &&
|
||||
v0.col == v3.col &&
|
||||
v0.col == v4.col &&
|
||||
v0.col == v5.col;
|
||||
|
||||
const bool has_texture =
|
||||
v0.uv != white_uv ||
|
||||
v1.uv != white_uv ||
|
||||
v2.uv != white_uv ||
|
||||
v3.uv != white_uv ||
|
||||
v4.uv != white_uv ||
|
||||
v5.uv != white_uv;
|
||||
|
||||
min.x = std::max(min.x, pcmd.ClipRect.x);
|
||||
min.y = std::max(min.y, pcmd.ClipRect.y);
|
||||
max.x = std::min(max.x, pcmd.ClipRect.z - 0.5f);
|
||||
max.y = std::min(max.y, pcmd.ClipRect.w - 0.5f);
|
||||
|
||||
if (max.x < min.x || max.y < min.y) { i += 6; continue; } // Completely clipped
|
||||
|
||||
const auto num_pixels = (max.x - min.x) * (max.y - min.y) * target.scale.x * target.scale.y;
|
||||
|
||||
if (has_uniform_color) {
|
||||
if (has_texture) {
|
||||
stats->textured_rectangle_pixels += num_pixels;
|
||||
} else {
|
||||
paint_uniform_rectangle(target, min, max, ColorInt(v0.col), stats);
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (has_texture) {
|
||||
// I have never encountered these.
|
||||
stats->gradient_textured_rectangle_pixels += num_pixels;
|
||||
} else {
|
||||
// Color picker. TODO: Optimize
|
||||
stats->gradient_rectangle_pixels += num_pixels;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool has_texture = (v0.uv != white_uv || v1.uv != white_uv || v2.uv != white_uv);
|
||||
paint_triangle(target, has_texture ? texture : nullptr, pcmd.ClipRect, v0, v1, v2, stats);
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void paint_draw_list(const PaintTarget& target, const ImDrawList* cmd_list, const SwOptions& options, Stats* stats)
|
||||
{
|
||||
const ImDrawIdx* idx_buffer = &cmd_list->IdxBuffer[0];
|
||||
const ImDrawVert* vertices = cmd_list->VtxBuffer.Data;
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.size(); cmd_i++)
|
||||
{
|
||||
const ImDrawCmd& pcmd = cmd_list->CmdBuffer[cmd_i];
|
||||
if (pcmd.UserCallback) {
|
||||
pcmd.UserCallback(cmd_list, &pcmd);
|
||||
} else {
|
||||
paint_draw_cmd(target, vertices, idx_buffer, pcmd, options, stats);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void make_style_fast()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
style.AntiAliasedLines = false;
|
||||
style.AntiAliasedFill = false;
|
||||
style.WindowRounding = 0;
|
||||
}
|
||||
|
||||
void restore_style()
|
||||
{
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
const ImGuiStyle default_style = ImGuiStyle();
|
||||
style.AntiAliasedLines = default_style.AntiAliasedLines;
|
||||
style.AntiAliasedFill = default_style.AntiAliasedFill;
|
||||
style.WindowRounding = default_style.WindowRounding;
|
||||
}
|
||||
|
||||
void bind_imgui_painting()
|
||||
{
|
||||
// make sure it doesn't get called twice
|
||||
static bool done = false;
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
done = true;
|
||||
|
||||
// Load default font (embedded in code):
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
uint8_t* tex_data;
|
||||
int font_width, font_height;
|
||||
io.Fonts->GetTexDataAsAlpha8(&tex_data, &font_width, &font_height);
|
||||
const auto texture = new Texture{tex_data, font_width, font_height};
|
||||
io.Fonts->TexID = texture;
|
||||
}
|
||||
|
||||
static Stats s_stats; // TODO: pass as an argument?
|
||||
|
||||
void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const SwOptions& options)
|
||||
{
|
||||
const float width_points = ImGui::GetIO().DisplaySize.x;
|
||||
const float height_points = ImGui::GetIO().DisplaySize.y;
|
||||
const ImVec2 scale{width_pixels / width_points, height_pixels / height_points};
|
||||
PaintTarget target{pixels, width_pixels, height_pixels, scale};
|
||||
const ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
|
||||
s_stats = Stats{};
|
||||
for (int i = 0; i < draw_data->CmdListsCount; ++i) {
|
||||
paint_draw_list(target, draw_data->CmdLists[i], options, &s_stats);
|
||||
}
|
||||
}
|
||||
|
||||
void unbind_imgui_painting()
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
delete reinterpret_cast<Texture*>(io.Fonts->TexID);
|
||||
io.Fonts = nullptr;
|
||||
}
|
||||
|
||||
bool show_options(SwOptions* io_options)
|
||||
{
|
||||
bool changed = false;
|
||||
changed |= ImGui::Checkbox("optimize_text", &io_options->optimize_text);
|
||||
changed |= ImGui::Checkbox("optimize_rectangles", &io_options->optimize_rectangles);
|
||||
return changed;
|
||||
}
|
||||
|
||||
void show_stats()
|
||||
{
|
||||
ImGui::Text("uniform_triangle_pixels: %7d", s_stats.uniform_triangle_pixels);
|
||||
ImGui::Text("textured_triangle_pixels: %7d", s_stats.textured_triangle_pixels);
|
||||
ImGui::Text("gradient_triangle_pixels: %7d", s_stats.gradient_triangle_pixels);
|
||||
ImGui::Text("font_pixels: %7d", s_stats.font_pixels);
|
||||
ImGui::Text("uniform_rectangle_pixels: %7.0f", s_stats.uniform_rectangle_pixels);
|
||||
ImGui::Text("textured_rectangle_pixels: %7.0f", s_stats.textured_rectangle_pixels);
|
||||
ImGui::Text("gradient_rectangle_pixels: %7.0f", s_stats.gradient_rectangle_pixels);
|
||||
ImGui::Text("gradient_textured_rectangle_pixels: %7.0f", s_stats.gradient_textured_rectangle_pixels);
|
||||
}
|
||||
|
||||
Stats get_stats() {
|
||||
return s_stats;
|
||||
}
|
||||
|
||||
} // namespace imgui_sw
|
||||
65
overlay/imgui/impl_sw.h
Normal file
65
overlay/imgui/impl_sw.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Original File By Emil Ernerfeldt 2018
|
||||
// https://github.com/emilk/imgui_software_renderer
|
||||
// LICENSE:
|
||||
// This software is dual-licensed to the public domain and under the following
|
||||
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||
// publish, and distribute this file as you see fit.
|
||||
// WHAT:
|
||||
// This is a software renderer for Dear ImGui.
|
||||
// It is decently fast, but has a lot of room for optimization.
|
||||
// The goal was to get something fast and decently accurate in not too many lines of code.
|
||||
// LIMITATIONS:
|
||||
// * It is not pixel-perfect, but it is good enough for must use cases.
|
||||
// * It does not support painting with any other texture than the default font texture.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace imgui_sw {
|
||||
|
||||
struct SwOptions
|
||||
{
|
||||
bool optimize_text = true; // No reason to turn this off.
|
||||
bool optimize_rectangles = true; // No reason to turn this off.
|
||||
};
|
||||
|
||||
|
||||
struct Stats
|
||||
{
|
||||
int uniform_triangle_pixels = 0;
|
||||
int textured_triangle_pixels = 0;
|
||||
int gradient_triangle_pixels = 0;
|
||||
int font_pixels = 0;
|
||||
double uniform_rectangle_pixels = 0;
|
||||
double textured_rectangle_pixels = 0;
|
||||
double gradient_rectangle_pixels = 0;
|
||||
double gradient_textured_rectangle_pixels = 0;
|
||||
};
|
||||
|
||||
/// Optional: tweak ImGui style to make it render faster.
|
||||
void make_style_fast();
|
||||
|
||||
/// Undo what make_style_fast did.
|
||||
void restore_style();
|
||||
|
||||
/// Call once a the start of your program.
|
||||
void bind_imgui_painting();
|
||||
|
||||
/// The buffer is assumed to follow how ImGui packs pixels, i.e. ABGR by default.
|
||||
/// Change with IMGUI_USE_BGRA_PACKED_COLOR.
|
||||
/// If width/height differs from ImGui::GetIO().DisplaySize then
|
||||
/// the function scales the UI to fit the given pixel buffer.
|
||||
void paint_imgui(uint32_t* pixels, int width_pixels, int height_pixels, const SwOptions& options = {});
|
||||
|
||||
/// Free the resources allocated by bind_imgui_painting.
|
||||
void unbind_imgui_painting();
|
||||
|
||||
/// Show ImGui controls for rendering options if you want to.
|
||||
bool show_options(SwOptions* io_options);
|
||||
|
||||
/// Show rendering stats in an ImGui window if you want to.
|
||||
void show_stats();
|
||||
|
||||
Stats get_stats();
|
||||
|
||||
} // namespace imgui_sw
|
||||
Reference in New Issue
Block a user