Build & setup: - Fix setup.sh and build.sh for macOS (bundle daemon, xmrig, sapling params, asmap.dat into .app) - Fix CMakeLists.txt libsodium linking for macOS - Fix incbin.h to use __DATA,__const section on macOS - Remove vendored libsodium-1.0.18 source tree (use fetch script instead) - Remove prebuilt-binaries/xmrig (replaced by xmrig-hac) - Add .DS_Store to .gitignore Rendering & UI: - Use GLSL #version 150 and OpenGL 3.2 Core Profile on macOS - Force dpiScale=1.0 on macOS to fix Retina double-scaling - Set default window/UI opacity to 100% on Mac/Linux - Add scroll fade shader guard for macOS GL compatibility - Add ImGui error recovery around render loop and mining tab Daemon & bootstrap: - Fix getDragonXDataDir() to return ~/Library/Application Support/Hush/DRAGONX/ on macOS - Fix isPortInUse() with connect() fallback (no /proc/net/tcp on macOS) - Increase daemon watchdog timeout from 3s to 15s - Add daemon status indicator (colored dot + label) in wizard bootstrap phases Mining tab: - Fix EmbeddedDaemon::getMemoryUsageMB() crash on macOS (was using Linux /proc) - Fix XmrigManager::getMemoryUsageMB() to use ps on macOS instead of /proc - Restructure RenderMiningTab with wrapper pattern for exception safety - Fix default pool URL to include port (pool.dragonx.is:3433)
425 lines
16 KiB
C++
425 lines
16 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
// Shader-based scroll-edge fade effect.
|
|
//
|
|
// Inserts ImDrawCallback entries into the draw list to switch from ImGui's
|
|
// default shader to a custom shader that multiplies output alpha by a
|
|
// smoothstep gradient based on screen-Y position. After the fade region,
|
|
// ImDrawCallback_ResetRenderState restores ImGui's default render state.
|
|
//
|
|
// GL path: custom GLSL 130 program (VS + FS), uses gl_FragCoord.y
|
|
// DX11 path: custom pixel shader only, uses SV_POSITION.y
|
|
|
|
#pragma once
|
|
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
|
|
// ---- Platform headers ----
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
#include <glad/gl.h>
|
|
#endif
|
|
|
|
#ifdef DRAGONX_USE_DX11
|
|
#include <d3d11.h>
|
|
#include <d3dcompiler.h>
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstddef>
|
|
#include "../../util/logger.h"
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
namespace effects {
|
|
|
|
// ============================================================================
|
|
// ScrollFadeShader — per-pixel scroll-edge alpha mask via ImDrawCallback
|
|
// ============================================================================
|
|
|
|
struct ScrollFadeShader {
|
|
// ---- Caller-set parameters (logical screen-space, per-frame) ----
|
|
float fadeTopY = 0.0f; // top of scrollable region (screen Y)
|
|
float fadeBottomY = 0.0f; // bottom of scrollable region (screen Y)
|
|
float fadeZoneTop = 0.0f; // top fade height (0 = disabled)
|
|
float fadeZoneBottom = 0.0f; // bottom fade height (0 = disabled)
|
|
|
|
// ---- Internal state ----
|
|
bool ready = false;
|
|
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
GLuint program_ = 0;
|
|
GLint locProjMtx_ = -1;
|
|
GLint locTexture_ = -1;
|
|
GLint locFadeTopY_ = -1;
|
|
GLint locFadeBottomY_= -1;
|
|
GLint locFadeZoneTop_= -1;
|
|
GLint locFadeZoneBot_= -1;
|
|
GLint locViewportH_ = -1;
|
|
#endif
|
|
|
|
#ifdef DRAGONX_USE_DX11
|
|
ID3D11PixelShader* pFadePS_ = nullptr;
|
|
ID3D11Buffer* pFadeCB_ = nullptr;
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------
|
|
// Init / Destroy
|
|
// ----------------------------------------------------------------
|
|
|
|
bool init() {
|
|
if (ready) return true;
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
ready = initGL();
|
|
#elif defined(DRAGONX_USE_DX11)
|
|
ready = initDX11();
|
|
#endif
|
|
return ready;
|
|
}
|
|
|
|
void destroy() {
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
destroyGL();
|
|
#elif defined(DRAGONX_USE_DX11)
|
|
destroyDX11();
|
|
#endif
|
|
ready = false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
// Draw-list integration helpers
|
|
// ----------------------------------------------------------------
|
|
|
|
/// Insert a "bind fade shader" callback into the draw list.
|
|
/// Must be followed later by addUnbind() or AddCallback(ImDrawCallback_ResetRenderState).
|
|
void addBind(ImDrawList* dl) {
|
|
dl->AddCallback(BindCB, this);
|
|
}
|
|
|
|
/// Insert ImDrawCallback_ResetRenderState to restore ImGui's default shader.
|
|
static void addUnbind(ImDrawList* dl) {
|
|
dl->AddCallback(ImDrawCallback_ResetRenderState, nullptr);
|
|
}
|
|
|
|
// ================================================================
|
|
// GL implementation
|
|
// ================================================================
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
|
|
static constexpr const char* kFadeVS_130 =
|
|
#ifdef __APPLE__
|
|
"#version 150\n"
|
|
#else
|
|
"#version 130\n"
|
|
#endif
|
|
"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"
|
|
" Frag_UV = UV;\n"
|
|
" Frag_Color = Color;\n"
|
|
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
|
|
"}\n";
|
|
|
|
static constexpr const char* kFadeFS_130 =
|
|
#ifdef __APPLE__
|
|
"#version 150\n"
|
|
#else
|
|
"#version 130\n"
|
|
#endif
|
|
"uniform sampler2D Texture;\n"
|
|
"uniform float u_fadeTopY;\n"
|
|
"uniform float u_fadeBottomY;\n"
|
|
"uniform float u_fadeZoneTop;\n"
|
|
"uniform float u_fadeZoneBot;\n"
|
|
"uniform float u_viewportH;\n"
|
|
"in vec2 Frag_UV;\n"
|
|
"in vec4 Frag_Color;\n"
|
|
"out vec4 Out_Color;\n"
|
|
"void main() {\n"
|
|
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
|
|
" float y = u_viewportH - gl_FragCoord.y;\n" // bottom-up → top-down
|
|
" float topA = (u_fadeZoneTop > 0.0)\n"
|
|
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
|
|
" : 1.0;\n"
|
|
" float botA = (u_fadeZoneBot > 0.0)\n"
|
|
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
|
|
" : 1.0;\n"
|
|
" Out_Color.a *= topA * botA;\n"
|
|
"}\n";
|
|
|
|
bool initGL() {
|
|
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
|
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(vs, 1, &kFadeVS_130, nullptr);
|
|
glCompileShader(vs);
|
|
if (!checkShader(vs, "fade VS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
|
|
|
|
glShaderSource(fs, 1, &kFadeFS_130, nullptr);
|
|
glCompileShader(fs);
|
|
if (!checkShader(fs, "fade FS")) { glDeleteShader(vs); glDeleteShader(fs); return false; }
|
|
|
|
program_ = glCreateProgram();
|
|
glAttachShader(program_, vs);
|
|
glAttachShader(program_, fs);
|
|
|
|
// Force attribute locations to match ImGui's typical assignment
|
|
// (Position=0, UV=1, Color=2) so that the VAO set up by ImGui's
|
|
// backend is compatible with our program.
|
|
glBindAttribLocation(program_, 0, "Position");
|
|
glBindAttribLocation(program_, 1, "UV");
|
|
glBindAttribLocation(program_, 2, "Color");
|
|
|
|
glLinkProgram(program_);
|
|
glDetachShader(program_, vs);
|
|
glDetachShader(program_, fs);
|
|
glDeleteShader(vs);
|
|
glDeleteShader(fs);
|
|
|
|
if (!checkProgram(program_, "fade program")) {
|
|
glDeleteProgram(program_);
|
|
program_ = 0;
|
|
return false;
|
|
}
|
|
|
|
locProjMtx_ = glGetUniformLocation(program_, "ProjMtx");
|
|
locTexture_ = glGetUniformLocation(program_, "Texture");
|
|
locFadeTopY_ = glGetUniformLocation(program_, "u_fadeTopY");
|
|
locFadeBottomY_ = glGetUniformLocation(program_, "u_fadeBottomY");
|
|
locFadeZoneTop_ = glGetUniformLocation(program_, "u_fadeZoneTop");
|
|
locFadeZoneBot_ = glGetUniformLocation(program_, "u_fadeZoneBot");
|
|
locViewportH_ = glGetUniformLocation(program_, "u_viewportH");
|
|
|
|
DEBUG_LOGF("ScrollFadeShader: GL program %u (ProjMtx=%d Tex=%d top=%d bot=%d zt=%d zb=%d vh=%d)\n",
|
|
program_, locProjMtx_, locTexture_,
|
|
locFadeTopY_, locFadeBottomY_, locFadeZoneTop_, locFadeZoneBot_, locViewportH_);
|
|
return true;
|
|
}
|
|
|
|
void destroyGL() {
|
|
if (program_) { glDeleteProgram(program_); program_ = 0; }
|
|
}
|
|
|
|
static bool checkShader(GLuint s, const char* label) {
|
|
GLint ok = 0;
|
|
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
|
if (!ok) {
|
|
char log[512];
|
|
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
|
|
DEBUG_LOGF("ScrollFadeShader: %s compile error:\n%s\n", label, log);
|
|
}
|
|
return ok != 0;
|
|
}
|
|
|
|
static bool checkProgram(GLuint p, const char* label) {
|
|
GLint ok = 0;
|
|
glGetProgramiv(p, GL_LINK_STATUS, &ok);
|
|
if (!ok) {
|
|
char log[512];
|
|
glGetProgramInfoLog(p, sizeof(log), nullptr, log);
|
|
DEBUG_LOGF("ScrollFadeShader: %s link error:\n%s\n", label, log);
|
|
}
|
|
return ok != 0;
|
|
}
|
|
|
|
#endif // DRAGONX_HAS_GLAD
|
|
|
|
// ================================================================
|
|
// DX11 implementation
|
|
// ================================================================
|
|
#ifdef DRAGONX_USE_DX11
|
|
|
|
// Pixel-shader constant buffer layout (must be 16-byte aligned)
|
|
struct alignas(16) FadeCBData {
|
|
float fadeTopY;
|
|
float fadeBottomY;
|
|
float fadeZoneTop;
|
|
float fadeZoneBot;
|
|
};
|
|
|
|
static constexpr const char* kFadePS_HLSL =
|
|
"cbuffer FadeParams : register(b1) {\n"
|
|
" float u_fadeTopY;\n"
|
|
" float u_fadeBottomY;\n"
|
|
" float u_fadeZoneTop;\n"
|
|
" float u_fadeZoneBot;\n"
|
|
"};\n"
|
|
"struct PS_INPUT {\n"
|
|
" float4 pos : SV_POSITION;\n"
|
|
" float4 col : COLOR0;\n"
|
|
" float2 uv : TEXCOORD0;\n"
|
|
"};\n"
|
|
"sampler sampler0;\n"
|
|
"Texture2D texture0;\n"
|
|
"float4 main(PS_INPUT input) : SV_Target {\n"
|
|
" float4 out_col = input.col * texture0.Sample(sampler0, input.uv);\n"
|
|
" float y = input.pos.y;\n" // SV_POSITION.y is top-down in FB pixels
|
|
" float topA = (u_fadeZoneTop > 0.0)\n"
|
|
" ? smoothstep(u_fadeTopY, u_fadeTopY + u_fadeZoneTop, y)\n"
|
|
" : 1.0;\n"
|
|
" float botA = (u_fadeZoneBot > 0.0)\n"
|
|
" ? (1.0 - smoothstep(u_fadeBottomY - u_fadeZoneBot, u_fadeBottomY, y))\n"
|
|
" : 1.0;\n"
|
|
" out_col.a *= topA * botA;\n"
|
|
" return out_col;\n"
|
|
"}\n";
|
|
|
|
bool initDX11() {
|
|
// Get DX11 device from ImGui backend
|
|
auto* bd = ImGui::GetIO().BackendRendererUserData;
|
|
if (!bd) { DEBUG_LOGF("ScrollFadeShader: no DX11 backend data\n"); return false; }
|
|
|
|
// ImGui_ImplDX11_Data layout: first field is ID3D11Device*
|
|
ID3D11Device* device = *(ID3D11Device**)bd;
|
|
if (!device) { DEBUG_LOGF("ScrollFadeShader: null DX11 device\n"); return false; }
|
|
|
|
// Compile pixel shader
|
|
ID3DBlob* psBlob = nullptr;
|
|
ID3DBlob* errBlob = nullptr;
|
|
HRESULT hr = D3DCompile(kFadePS_HLSL, strlen(kFadePS_HLSL),
|
|
nullptr, nullptr, nullptr,
|
|
"main", "ps_4_0", 0, 0,
|
|
&psBlob, &errBlob);
|
|
if (FAILED(hr)) {
|
|
if (errBlob) {
|
|
DEBUG_LOGF("ScrollFadeShader: PS compile error:\n%s\n",
|
|
(const char*)errBlob->GetBufferPointer());
|
|
errBlob->Release();
|
|
}
|
|
return false;
|
|
}
|
|
if (errBlob) errBlob->Release();
|
|
|
|
hr = device->CreatePixelShader(psBlob->GetBufferPointer(),
|
|
psBlob->GetBufferSize(),
|
|
nullptr, &pFadePS_);
|
|
psBlob->Release();
|
|
if (FAILED(hr)) {
|
|
DEBUG_LOGF("ScrollFadeShader: CreatePixelShader failed 0x%08lx\n", hr);
|
|
return false;
|
|
}
|
|
|
|
// Create constant buffer for fade params
|
|
D3D11_BUFFER_DESC cbDesc = {};
|
|
cbDesc.ByteWidth = sizeof(FadeCBData);
|
|
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
|
|
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
|
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
|
hr = device->CreateBuffer(&cbDesc, nullptr, &pFadeCB_);
|
|
if (FAILED(hr)) {
|
|
DEBUG_LOGF("ScrollFadeShader: CreateBuffer (CB) failed 0x%08lx\n", hr);
|
|
pFadePS_->Release(); pFadePS_ = nullptr;
|
|
return false;
|
|
}
|
|
|
|
VERBOSE_LOGF("ScrollFadeShader: DX11 pixel shader + CB created\n");
|
|
return true;
|
|
}
|
|
|
|
void destroyDX11() {
|
|
if (pFadeCB_) { pFadeCB_->Release(); pFadeCB_ = nullptr; }
|
|
if (pFadePS_) { pFadePS_->Release(); pFadePS_ = nullptr; }
|
|
}
|
|
|
|
#endif // DRAGONX_USE_DX11
|
|
|
|
// ================================================================
|
|
// Callback — called by ImGui's render backend during RenderDrawData
|
|
// ================================================================
|
|
|
|
static void BindCB(const ImDrawList* /*dl*/, const ImDrawCmd* cmd) {
|
|
auto* self = static_cast<ScrollFadeShader*>(cmd->UserCallbackData);
|
|
if (!self || !self->ready) return;
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
float fbScaleY = io.DisplayFramebufferScale.y;
|
|
float fb_height = io.DisplaySize.y * fbScaleY;
|
|
|
|
// In multi-viewport mode ImGui coordinates are OS-absolute, but
|
|
// SV_POSITION / gl_FragCoord are render-target-relative. Subtract
|
|
// the main viewport origin to convert screen-space → RT-local.
|
|
float vpY = ImGui::GetMainViewport()->Pos.y;
|
|
|
|
// Convert logical screen-space params to framebuffer pixels
|
|
float topY = (self->fadeTopY - vpY) * fbScaleY;
|
|
float botY = (self->fadeBottomY - vpY) * fbScaleY;
|
|
float zoneTop = self->fadeZoneTop * fbScaleY;
|
|
float zoneBot = self->fadeZoneBottom * fbScaleY;
|
|
|
|
#ifdef DRAGONX_HAS_GLAD
|
|
glUseProgram(self->program_);
|
|
|
|
// Rebind vertex attribute pointers so our forced locations (0=Position,
|
|
// 1=UV, 2=Color) match the already-bound VBO data. ImGui's linker may
|
|
// assign different locations (e.g. Mesa uses alphabetical order:
|
|
// Color=0, Position=1, UV=2), but the VBO layout is fixed, so we
|
|
// must explicitly point our attributes at the correct offsets.
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, pos));
|
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, uv));
|
|
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)offsetof(ImDrawVert, col));
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
glEnableVertexAttribArray(2);
|
|
|
|
// Ortho projection — must match ImGui's SetupRenderState which uses
|
|
// draw_data->DisplayPos as the origin. With ViewportsEnable, vertex
|
|
// positions are in OS screen-space; the projection matrix maps them
|
|
// to window-local framebuffer coordinates.
|
|
float vpX = ImGui::GetMainViewport()->Pos.x;
|
|
float L = vpX;
|
|
float R = vpX + io.DisplaySize.x;
|
|
float T = vpY;
|
|
float B = vpY + io.DisplaySize.y;
|
|
const float ortho[4][4] = {
|
|
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
|
|
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
|
|
{ 0.0f, 0.0f, -1.0f, 0.0f },
|
|
{ (R+L)/(L-R), (T+B)/(B-T), 0.0f, 1.0f },
|
|
};
|
|
glUniformMatrix4fv(self->locProjMtx_, 1, GL_FALSE, &ortho[0][0]);
|
|
glUniform1i(self->locTexture_, 0);
|
|
glUniform1f(self->locFadeTopY_, topY);
|
|
glUniform1f(self->locFadeBottomY_, botY);
|
|
glUniform1f(self->locFadeZoneTop_, zoneTop);
|
|
glUniform1f(self->locFadeZoneBot_, zoneBot);
|
|
glUniform1f(self->locViewportH_, fb_height);
|
|
#endif
|
|
|
|
#ifdef DRAGONX_USE_DX11
|
|
(void)fb_height;
|
|
// Get device context from ImGui backend (first two fields of ImGui_ImplDX11_Data)
|
|
auto* bd = io.BackendRendererUserData;
|
|
if (!bd) return;
|
|
struct DX11BD { ID3D11Device* dev; ID3D11DeviceContext* ctx; };
|
|
auto* dx = reinterpret_cast<DX11BD*>(bd);
|
|
ID3D11DeviceContext* ctx = dx->ctx;
|
|
if (!ctx) return;
|
|
|
|
// Update constant buffer
|
|
D3D11_MAPPED_SUBRESOURCE mapped;
|
|
if (SUCCEEDED(ctx->Map(self->pFadeCB_, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped))) {
|
|
auto* cb = static_cast<FadeCBData*>(mapped.pData);
|
|
cb->fadeTopY = topY;
|
|
cb->fadeBottomY = botY;
|
|
cb->fadeZoneTop = zoneTop;
|
|
cb->fadeZoneBot = zoneBot;
|
|
ctx->Unmap(self->pFadeCB_, 0);
|
|
}
|
|
|
|
// Bind our pixel shader + constant buffer (slot 1 = register(b1))
|
|
ctx->PSSetShader(self->pFadePS_, nullptr, 0);
|
|
ctx->PSSetConstantBuffers(1, 1, &self->pFadeCB_);
|
|
#endif
|
|
}
|
|
};
|
|
|
|
} // namespace effects
|
|
} // namespace ui
|
|
} // namespace dragonx
|