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

Scalable SDF fonts & shadows #4056

Open
wants to merge 18 commits into
base: master
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
141 changes: 133 additions & 8 deletions backends/imgui_impl_dx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,10 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
if (bd->pFontSampler)
ImGui_ImplDX11_InvalidateDeviceObjects();

#ifndef IMGUI_DISABLE_SDF
bool sdf = (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_SignedDistanceFonts) | (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_SignedDistanceShapes);
#endif

// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
// If you would like to use this DX11 sample code but remove this dependency you can:
// 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution]
Expand All @@ -380,7 +384,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()

// Create the vertex shader
{
static const char* vertexShader =
static const char* vertexShaderDirect =
"cbuffer vertexBuffer : register(b0) \
{\
float4x4 ProjectionMatrix; \
Expand Down Expand Up @@ -408,9 +412,63 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
return output;\
}";

#ifndef IMGUI_DISABLE_SDF
static const char* vertexShaderSDF =
"cbuffer vertexBuffer : register(b0) \
{\
float4x4 ProjectionMatrix; \
};\
struct VS_INPUT\
{\
float2 pos : POSITION;\
float2 uv : TEXCOORD0;\
float4 innerColor : COLOR0;\
float4 startOuterColor : COLOR1;\
float4 endOuterColor : COLOR2;\
float a : COLOR3; \
float b : COLOR4; \
float w : COLOR5; \
};\
\
struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float2 uv : TEXCOORD0;\
float4 innerColor : COLOR0;\
float4 startOuterColor : COLOR1;\
float4 endOuterColor : COLOR2;\
nointerpolation float a : COLOR3; \
nointerpolation float b : COLOR4; \
nointerpolation float w : COLOR5; \
};\
\
PS_INPUT main(VS_INPUT input)\
{\
PS_INPUT output;\
output.pos = mul(ProjectionMatrix, float4(input.pos.xy, 0.f, 1.f));\
output.uv = input.uv;\
output.innerColor = input.innerColor;\
output.startOuterColor = input.startOuterColor;\
output.endOuterColor = input.endOuterColor;\
output.a = input.a;\
output.b = input.b;\
output.w = input.w;\
return output;\
}";

const char* vertexShader = sdf ? vertexShaderSDF : vertexShaderDirect;
#else
const char* vertexShader = vertexShaderDirect;
#endif

ID3DBlob* vertexShaderBlob;
if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vertexShaderBlob, NULL)))
ID3DBlob* pError;
if (FAILED(D3DCompile(vertexShader, strlen(vertexShader), NULL, NULL, NULL, "main", "vs_4_0", 0, 0, &vertexShaderBlob, &pError))) {
if (pError) {
fprintf(stderr, "VertexShader: %s\n", (char*)pError->GetBufferPointer());
}
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
}
if (bd->pd3dDevice->CreateVertexShader(vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), NULL, &bd->pVertexShader) != S_OK)
{
vertexShaderBlob->Release();
Expand All @@ -420,12 +478,24 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
// Create the input layout
D3D11_INPUT_ELEMENT_DESC local_layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, pos), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, col), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, uv), D3D11_INPUT_PER_VERTEX_DATA, 0 },
#ifndef IMGUI_DISABLE_SDF
{ "COLOR", 1, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, startOuterColor), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 2, DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT)IM_OFFSETOF(ImDrawVert, endOuterColor), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 3, DXGI_FORMAT_R32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, a), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 4, DXGI_FORMAT_R32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, b), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 5, DXGI_FORMAT_R32_FLOAT, 0, (UINT)IM_OFFSETOF(ImDrawVert, w), D3D11_INPUT_PER_VERTEX_DATA, 0 },
#endif
};
#ifndef IMGUI_DISABLE_SDF
if (bd->pd3dDevice->CreateInputLayout(local_layout, sdf ? 8 : 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
#else
if (bd->pd3dDevice->CreateInputLayout(local_layout, 3, vertexShaderBlob->GetBufferPointer(), vertexShaderBlob->GetBufferSize(), &bd->pInputLayout) != S_OK)
#endif
{
assert(false);
vertexShaderBlob->Release();
return false;
}
Expand All @@ -445,7 +515,7 @@ bool ImGui_ImplDX11_CreateDeviceObjects()

// Create the pixel shader
{
static const char* pixelShader =
static const char* pixelShaderDirect =
"struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
Expand All @@ -461,9 +531,61 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
return out_col; \
}";

#ifndef IMGUI_DISABLE_SDF
static const char* pixelShaderSDF =
"struct PS_INPUT\
{\
float4 pos : SV_POSITION;\
float2 uv : TEXCOORD0;\
float4 innerColor : COLOR0;\
float4 startOuterColor : COLOR1;\
float4 endOuterColor : COLOR2;\
nointerpolation float a : COLOR3; \
nointerpolation float b : COLOR4; \
nointerpolation float w : COLOR5; \
}; \
sampler sampler0; \
Texture2D texture0; \
\
float stretch(float low, float high, float x) {\n"
" return clamp((x-low)/(high-low), 0.0, 1.0);\
}\
[earlydepthstencil] float4 main(PS_INPUT input) : SV_Target {\
if (input.a == 0.0) \
return input.innerColor * texture0.Sample(sampler0, input.uv); \
float distance;\
if (input.a >= 2.0) {\
input.a = input.a - 2.0;\
distance = 1.0 - clamp(length(input.uv), 0.0, 1.0);\
} else {\
distance = texture0.Sample(sampler0, input.uv).a;\
}\
if (distance >= input.a + input.w) return input.innerColor;\
if (distance <= input.b - input.w) discard; \
float m = stretch(input.a - input.w, min(1.0, input.a + input.w), distance);\
if (input.a <= input.b) \
return float4(input.innerColor.rgb, input.innerColor.a * m);\
float outerMix = stretch(input.b, input.a, distance);\
float4 outer = lerp(input.endOuterColor, input.startOuterColor, outerMix);\
outer.a *= stretch(input.b - input.w, input.b + input.w, distance);\
float ia = m * input.innerColor.a;\
float oa = (1 - m) * outer.a;\
float a = ia + oa;\
return float4((input.innerColor.rgb * ia + outer.rgb * oa) / a, a);\
}";
const char* pixelShader = sdf ? pixelShaderSDF : pixelShaderDirect;
#else
const char* pixelShader = pixelShaderDirect;
#endif

ID3DBlob* pixelShaderBlob;
if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &pixelShaderBlob, NULL)))
ID3DBlob* pError;
if (FAILED(D3DCompile(pixelShader, strlen(pixelShader), NULL, NULL, NULL, "main", "ps_4_0", 0, 0, &pixelShaderBlob, &pError))) {
if (pError) {
fprintf(stderr, "PixelShader: %s\n", (char*)pError->GetBufferPointer());
}
return false; // NB: Pass ID3DBlob* pErrorBlob to D3DCompile() to get error showing in (const char*)pErrorBlob->GetBufferPointer(). Make sure to Release() the blob!
}
if (bd->pd3dDevice->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), NULL, &bd->pPixelShader) != S_OK)
{
pixelShaderBlob->Release();
Expand Down Expand Up @@ -537,7 +659,7 @@ void ImGui_ImplDX11_InvalidateDeviceObjects()
if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = NULL; }
}

bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context)
bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context, ImGuiBackendFlags flags)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendRendererUserData == NULL && "Already initialized a renderer backend!");
Expand All @@ -547,6 +669,9 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx11";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= (flags & ImGuiBackendFlags_SignedDistanceFonts);
io.BackendFlags |= (flags & ImGuiBackendFlags_SignedDistanceShapes);
io.BackendFlags |= ImGuiBackendFlags_ProvocingVertexFirst;

// Get factory from device
IDXGIDevice* pDXGIDevice = NULL;
Expand Down
2 changes: 1 addition & 1 deletion backends/imgui_impl_dx11.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
struct ID3D11Device;
struct ID3D11DeviceContext;

IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context);
IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context, ImGuiBackendFlags flags = ImGuiBackendFlags_DefaultFast);
IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
Expand Down
2 changes: 1 addition & 1 deletion backends/imgui_impl_metal.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
@class MTLRenderPassDescriptor;
@protocol MTLDevice, MTLCommandBuffer, MTLRenderCommandEncoder;

IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device);
IMGUI_IMPL_API bool ImGui_ImplMetal_Init(id<MTLDevice> device, ImGuiBackendFlags flags = ImGuiBackendFlags_DefaultFast);
IMGUI_IMPL_API void ImGui_ImplMetal_Shutdown();
IMGUI_IMPL_API void ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor);
IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
Expand Down
109 changes: 107 additions & 2 deletions backends/imgui_impl_metal.mm
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ - (void)renderDrawData:(ImDrawData *)drawData

#pragma mark - ImGui API implementation

bool ImGui_ImplMetal_Init(id<MTLDevice> device)
bool ImGui_ImplMetal_Init(id<MTLDevice> device, ImGuiBackendFlags flags)
{
ImGuiIO& io = ImGui::GetIO();
io.BackendRendererName = "imgui_impl_metal";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= (flags & ImGuiBackendFlags_SignedDistanceFonts);
io.BackendFlags |= (flags & ImGuiBackendFlags_SignedDistanceShapes);
io.BackendFlags |= ImGuiBackendFlags_ProvocingVertexFirst;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Expand Down Expand Up @@ -316,7 +319,7 @@ - (void)enqueueReusableBuffer:(MetalBuffer *)buffer
{
NSError *error = nil;

NSString *shaderSource = @""
NSString *shaderSourceDirect = @""
"#include <metal_stdlib>\n"
"using namespace metal;\n"
"\n"
Expand Down Expand Up @@ -352,6 +355,91 @@ - (void)enqueueReusableBuffer:(MetalBuffer *)buffer
" return half4(in.color) * texColor;\n"
"}\n";

#ifndef IMGUI_DISABLE_SDF
NSString *shaderSourceSDF = @""
"#include <metal_stdlib>\n"
"using namespace metal;\n"
"\n"
"struct Uniforms {\n"
" float4x4 projectionMatrix;\n"
"};\n"
"\n"
"struct VertexIn {\n"
" float2 position [[attribute(0)]];\n"
" float2 texCoords [[attribute(1)]];\n"
" uchar4 innerColor [[attribute(2)]];\n"
" uchar4 startOuterColor [[attribute(3)]];\n"
" uchar4 endOuterColor [[attribute(4)]];\n"
" float a [[attribute(5)]];\n"
" float b [[attribute(6)]];\n"
" float w [[attribute(7)]];\n"
"};\n"
"\n"
"struct VertexOut {\n"
" float4 position [[position]];\n"
" float2 texCoords;\n"
" float4 innerColor;\n"
" float4 startOuterColor;\n"
" float4 endOuterColor;\n"
" float a [[flat]];\n"
" float b [[flat]];\n"
" float w [[flat]];\n"
"};\n"
"\n"
"vertex VertexOut vertex_main(VertexIn in [[stage_in]],\n"
" constant Uniforms &uniforms [[buffer(1)]]) {\n"
" VertexOut out;\n"
" out.position = uniforms.projectionMatrix * float4(in.position, 0, 1);\n"
" out.texCoords = in.texCoords;\n"
" out.innerColor = float4(in.innerColor) / float4(255.0);\n"
" out.startOuterColor = float4(in.startOuterColor) / float4(255.0);\n"
" out.endOuterColor = float4(in.endOuterColor) / float4(255.0);\n"
" out.a = in.a;\n"
" out.b = in.b;\n"
" out.w = in.w;\n"
" return out;\n"
"}\n"
"\n"
"float stretch(float low, float high, float x) {\n"
" return clamp((x-low)/(high-low), 0.0, 1.0);\n"
"}\n"
"[[early_fragment_tests]] fragment float4 fragment_main(VertexOut in [[stage_in]],\n"
" texture2d<half, access::sample> texture [[texture(0)]]) {\n"
" constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
" if (in.a == 0) {\n"
" half4 c = texture.sample(linearSampler, in.texCoords);\n"
" return float4(c) * in.innerColor;\n"
" }\n"
" float distance;\n"
" if (in.a >= 2.0) {\n"
" in.a = in.a - 2.0;\n"
" distance = 1.0 - clamp(length(in.texCoords), 0.0, 1.0);\n"
" } else {\n"
" distance = texture.sample(linearSampler, in.texCoords).a;\n"
" }\n"
" if (distance >= in.a + in.w) {\n"
" return in.innerColor;\n"
" }\n"
" if (distance <= in.b - in.w) { discard_fragment(); return float4(0.0, 0.0, 0.0, 0.0); } \n"
" float m = stretch(in.a - in.w, min(1.0, in.a + in.w), distance); \n"
" if (in.a <= in.b) {\n"
" return float4(in.innerColor.rgb, in.innerColor.a * m);\n"
" }\n"
" float outerMix = stretch(in.b, in.a, distance); \n"
" float4 outer = mix(in.endOuterColor, in.startOuterColor, outerMix); \n"
" outer.a *= stretch(in.b - in.w, in.b + in.w, distance); \n"
" float ia = m * in.innerColor.a;\n"
" float oa = (1 - m) * outer.a;\n"
" float a = ia + oa;\n"
" return float4((in.innerColor.rgb * ia + outer.rgb * oa) / a, a);\n"
"}\n";

bool sdf = (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_SignedDistanceFonts) | (ImGui::GetIO().BackendFlags & ImGuiBackendFlags_SignedDistanceShapes);
NSString* shaderSource = sdf ? shaderSourceSDF : shaderSourceDirect;
#else
NSString* shaderSource = shaderSourceDirect;
#endif

id<MTLLibrary> library = [device newLibraryWithSource:shaderSource options:nil error:&error];
if (library == nil)
{
Expand All @@ -378,6 +466,23 @@ - (void)enqueueReusableBuffer:(MetalBuffer *)buffer
vertexDescriptor.attributes[2].offset = IM_OFFSETOF(ImDrawVert, col);
vertexDescriptor.attributes[2].format = MTLVertexFormatUChar4; // color
vertexDescriptor.attributes[2].bufferIndex = 0;
#ifndef IMGUI_DISABLE_SDF
vertexDescriptor.attributes[3].offset = IM_OFFSETOF(ImDrawVert, startOuterColor);
vertexDescriptor.attributes[3].format = MTLVertexFormatUChar4; // color
vertexDescriptor.attributes[3].bufferIndex = 0;
vertexDescriptor.attributes[4].offset = IM_OFFSETOF(ImDrawVert, endOuterColor);
vertexDescriptor.attributes[4].format = MTLVertexFormatUChar4; // color
vertexDescriptor.attributes[4].bufferIndex = 0;
vertexDescriptor.attributes[5].offset = IM_OFFSETOF(ImDrawVert, a);
vertexDescriptor.attributes[5].format = MTLVertexFormatFloat;
vertexDescriptor.attributes[5].bufferIndex = 0;
vertexDescriptor.attributes[6].offset = IM_OFFSETOF(ImDrawVert, b);
vertexDescriptor.attributes[6].format = MTLVertexFormatFloat;
vertexDescriptor.attributes[6].bufferIndex = 0;
vertexDescriptor.attributes[7].offset = IM_OFFSETOF(ImDrawVert, w);
vertexDescriptor.attributes[7].format = MTLVertexFormatFloat;
vertexDescriptor.attributes[7].bufferIndex = 0;
#endif
vertexDescriptor.layouts[0].stepRate = 1;
vertexDescriptor.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex;
vertexDescriptor.layouts[0].stride = sizeof(ImDrawVert);
Expand Down
Loading