native_render: HiDPI glyph pages, sharp UI upscaling, 4x MSAA
- CGraphicFontTexture rasterises glyph pages at the swapchain density (UISetFontOversample, MT_FONT_OVERSAMPLE) with anti-aliased coverage; metrics, packing and UVs stay the 40250 1x values. Default 1 keeps the verbatim bilevel path (Godot unchanged). - native.frag: sharp-bilinear sampling for magnified UI textures. - Multisampled colour/depth with a resolve into the swapchain (MT_MSAA, default 4); stats line reports msaa=N. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
22ca5da387
commit
52ccb80d01
@@ -5,6 +5,38 @@
|
||||
#include "EterBase/Stl.h"
|
||||
|
||||
#include "EterLib/Util.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
// PORT: HiDPI glyph pages (UISetFontOversample). With a scale s > 1 the page DIB and texture are
|
||||
// s times larger and each glyph is drawn with an s-times font into the s-scaled cell, while the
|
||||
// 40250 metrics (width, height, advance), the packing and therefore the page UVs stay the logical
|
||||
// 1x values: text layout is unchanged, only the texels get finer.
|
||||
namespace {
|
||||
int g_font_oversample = 1;
|
||||
bool g_font_oversample_latched = false;
|
||||
// The 1x HFONT a CGraphicFontTexture measures with -> its s-times twin that draws.
|
||||
std::unordered_map<HFONT, HFONT> g_oversampled_fonts;
|
||||
|
||||
int FontOversample()
|
||||
{
|
||||
g_font_oversample_latched = true;
|
||||
return g_font_oversample;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void UISetFontOversample(int scale)
|
||||
{
|
||||
if (g_font_oversample_latched)
|
||||
return;
|
||||
g_font_oversample = scale < 1 ? 1 : (scale > 4 ? 4 : scale);
|
||||
}
|
||||
|
||||
int UIFontOversample()
|
||||
{
|
||||
return g_font_oversample;
|
||||
}
|
||||
|
||||
CGraphicFontTexture::CGraphicFontTexture()
|
||||
{
|
||||
@@ -49,6 +81,13 @@ void CGraphicFontTexture::Destroy()
|
||||
|
||||
while(i != m_fontMap.end())
|
||||
{
|
||||
TFontMap::mapped_type hFont = i->second;
|
||||
auto hi = g_oversampled_fonts.find(hFont);
|
||||
if (hi != g_oversampled_fonts.end())
|
||||
{
|
||||
DeleteObject((HGDIOBJ)hi->second);
|
||||
g_oversampled_fonts.erase(hi);
|
||||
}
|
||||
DeleteObject((HGDIOBJ)i->second);
|
||||
++i;
|
||||
}
|
||||
@@ -86,7 +125,8 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI
|
||||
if (GetMaxTextureHeight() > 512)
|
||||
height = 512;
|
||||
|
||||
if (!m_dib.Create(ms_hDC, width, height))
|
||||
const int s = FontOversample();
|
||||
if (!m_dib.Create(ms_hDC, width * s, height * s))
|
||||
return false;
|
||||
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
@@ -139,6 +179,13 @@ HFONT CGraphicFontTexture::GetFont(WORD codePage)
|
||||
hFont = CreateFontIndirect(&logFont);
|
||||
|
||||
m_fontMap.insert(TFontMap::value_type(codePage, hFont));
|
||||
|
||||
if (const int s = FontOversample(); s > 1 && hFont)
|
||||
{
|
||||
logFont.lfHeight = m_fontSize * s;
|
||||
if (HFONT hHiFont = CreateFontIndirect(&logFont))
|
||||
g_oversampled_fonts[hFont] = hHiFont;
|
||||
}
|
||||
}
|
||||
|
||||
return hFont;
|
||||
@@ -210,7 +257,8 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::GetCharacterInfo
|
||||
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey code)
|
||||
{
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
SelectObject(hDC, GetFont(code.first));
|
||||
HFONT hFont = GetFont(code.first);
|
||||
SelectObject(hDC, hFont);
|
||||
|
||||
wchar_t keyValue = code.second;
|
||||
|
||||
@@ -232,8 +280,9 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
|
||||
LONG lAdvance = ceilf( stABC.abcfA + stABC.abcfB + stABC.abcfC );
|
||||
|
||||
int width = m_dib.GetWidth();
|
||||
int height = m_dib.GetHeight();
|
||||
const int s = FontOversample();
|
||||
int width = m_dib.GetWidth() / s;
|
||||
int height = m_dib.GetHeight() / s;
|
||||
|
||||
if (m_x + size.cx >= (width - 1))
|
||||
{
|
||||
@@ -255,6 +304,26 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
}
|
||||
}
|
||||
|
||||
auto hi = s > 1 ? g_oversampled_fonts.find(hFont) : g_oversampled_fonts.end();
|
||||
if (hi != g_oversampled_fonts.end())
|
||||
{
|
||||
// Draw with the s-times font into the s-scaled cell and keep its anti-aliasing as
|
||||
// A4R4G4B4 white with coverage alpha (1x keeps 40250's bilevel threshold below).
|
||||
SelectObject(hDC, hi->second);
|
||||
TextOutW(hDC, m_x * s, m_y * s, &keyValue, 1);
|
||||
SelectObject(hDC, hFont);
|
||||
|
||||
const int nDIBWidth = m_dib.GetWidth();
|
||||
DWORD* pdwRow = (DWORD*)m_dib.GetPointer() + nDIBWidth * (m_y * s) + m_x * s;
|
||||
for (int y = 0; y < size.cy * s; ++y, pdwRow += nDIBWidth)
|
||||
for (int x = 0; x < size.cx * s; ++x)
|
||||
{
|
||||
const DWORD alpha = ((pdwRow[x] & 0xff) * 15 + 127) / 255;
|
||||
pdwRow[x] = alpha ? ((alpha << 12) | 0x0fff) : 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TextOutW(hDC, m_x, m_y, &keyValue, 1);
|
||||
|
||||
int nChrX;
|
||||
@@ -276,6 +345,7 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
|
||||
pdwDIBRow[nChrX]=(pdwDIBRow[nChrX]&0xff) ? 0xffff : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float rhwidth = 1.0f / float(width);
|
||||
float rhheight = 1.0f / float(height);
|
||||
|
||||
@@ -72,3 +72,10 @@ void UIRenderAdd(UIRenderCommand command);
|
||||
const std::vector<UIRenderCommand>& UIRenderCommands();
|
||||
void UIRenderSetClip(float x, float y, float width, float height);
|
||||
void UIRenderRestoreClip();
|
||||
|
||||
// HiDPI text: CGraphicFontTexture rasterises its glyph pages at this multiple of the logical font
|
||||
// size (the swapchain's pixels per UI pixel) while keeping the 40250 glyph metrics and page UVs in
|
||||
// logical pixels. 1 (the default) is the verbatim 40250 path. Latched when the first font page is
|
||||
// created, so set it before the game creates fonts.
|
||||
void UISetFontOversample(int scale);
|
||||
int UIFontOversample();
|
||||
|
||||
+105
-30
@@ -942,6 +942,7 @@ public:
|
||||
if (depth_view_) vkDestroyImageView(device_, depth_view_, nullptr);
|
||||
if (depth_image_) vkDestroyImage(device_, depth_image_, nullptr);
|
||||
if (depth_memory_) vkFreeMemory(device_, depth_memory_, nullptr);
|
||||
destroy_msaa_color();
|
||||
for (auto view : image_views_) vkDestroyImageView(device_, view, nullptr);
|
||||
if (swapchain_) vkDestroySwapchainKHR(device_, swapchain_, nullptr);
|
||||
if (pool_) vkDestroyCommandPool(device_, pool_, nullptr);
|
||||
@@ -979,6 +980,7 @@ public:
|
||||
screenshot_frame_ = frame;
|
||||
}
|
||||
|
||||
int msaa_samples() const { return int(samples_); }
|
||||
std::uint32_t width() const { return extent_.width; }
|
||||
std::uint32_t height() const { return extent_.height; }
|
||||
std::uint32_t logical_width() const {
|
||||
@@ -1491,14 +1493,14 @@ public:
|
||||
vkCmdResetQueryPool(command_, query_pool_, 0, 2);
|
||||
vkCmdWriteTimestamp(command_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, query_pool_, 0);
|
||||
|
||||
VkClearValue clears[2]{};
|
||||
VkClearValue clears[3]{};
|
||||
// 40250 clears the back buffer to 0xff000000 once at device creation and afterwards
|
||||
// only clears depth each frame (CPythonApplication::Process -> ClearDepthBuffer).
|
||||
clears[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
||||
clears[1].depthStencil = {1.0f, 0};
|
||||
VkRenderPassBeginInfo pass_begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||||
pass_begin.renderPass = pass_; pass_begin.framebuffer = framebuffers_.at(image_index);
|
||||
pass_begin.renderArea.extent = extent_; pass_begin.clearValueCount = 2; pass_begin.pClearValues = clears;
|
||||
pass_begin.renderArea.extent = extent_; pass_begin.clearValueCount = samples_ != VK_SAMPLE_COUNT_1_BIT ? 3 : 2; pass_begin.pClearValues = clears;
|
||||
vkCmdBeginRenderPass(command_, &pass_begin, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkPipeline current_pipeline = VK_NULL_HANDLE;
|
||||
@@ -1709,19 +1711,12 @@ private:
|
||||
if (depth_view_) { vkDestroyImageView(device_, depth_view_, nullptr); depth_view_ = VK_NULL_HANDLE; }
|
||||
if (depth_image_) { vkDestroyImage(device_, depth_image_, nullptr); depth_image_ = VK_NULL_HANDLE; }
|
||||
if (depth_memory_) { vkFreeMemory(device_, depth_memory_, nullptr); depth_memory_ = VK_NULL_HANDLE; }
|
||||
destroy_msaa_color();
|
||||
for (auto view : image_views_) vkDestroyImageView(device_, view, nullptr);
|
||||
image_views_.clear();
|
||||
if (swapchain_) { vkDestroySwapchainKHR(device_, swapchain_, nullptr); swapchain_ = VK_NULL_HANDLE; }
|
||||
create_swapchain();
|
||||
for (auto view : image_views_) {
|
||||
VkImageView fb_attachments[2] = {view, depth_view_};
|
||||
VkFramebufferCreateInfo framebuffer{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebuffer.renderPass = pass_; framebuffer.attachmentCount = 2; framebuffer.pAttachments = fb_attachments;
|
||||
framebuffer.width = extent_.width; framebuffer.height = extent_.height; framebuffer.layers = 1;
|
||||
VkFramebuffer handle = VK_NULL_HANDLE;
|
||||
check(vkCreateFramebuffer(device_, &framebuffer, nullptr, &handle), "vkCreateFramebuffer recreate");
|
||||
framebuffers_.push_back(handle);
|
||||
}
|
||||
create_framebuffers();
|
||||
get_pipeline(0, 2, 0);
|
||||
}
|
||||
|
||||
@@ -1963,6 +1958,16 @@ private:
|
||||
enabled_features.samplerAnisotropy = VK_TRUE;
|
||||
max_anisotropy_ = std::min(16.0f, properties.limits.maxSamplerAnisotropy);
|
||||
}
|
||||
// Multisampled 3D edges; MT_MSAA=1 keeps the single-sample path, 2/4/8 request a count.
|
||||
{
|
||||
int wanted = 4;
|
||||
if (const char* env = std::getenv("MT_MSAA")) wanted = std::max(1, std::atoi(env));
|
||||
const VkSampleCountFlags supported =
|
||||
properties.limits.framebufferColorSampleCounts & properties.limits.framebufferDepthSampleCounts;
|
||||
samples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||
for (VkSampleCountFlagBits c : {VK_SAMPLE_COUNT_8_BIT, VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_2_BIT})
|
||||
if (int(c) <= wanted && (supported & c)) { samples_ = c; break; }
|
||||
}
|
||||
std::uint32_t extension_count = 0;
|
||||
check(vkEnumerateDeviceExtensionProperties(physical_, nullptr, &extension_count, nullptr), "device extensions count");
|
||||
std::vector<VkExtensionProperties> available(extension_count);
|
||||
@@ -2044,7 +2049,7 @@ private:
|
||||
depth_info.format = VK_FORMAT_D32_SFLOAT;
|
||||
depth_info.extent = {extent_.width, extent_.height, 1};
|
||||
depth_info.mipLevels = 1; depth_info.arrayLayers = 1;
|
||||
depth_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
depth_info.samples = samples_;
|
||||
depth_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
depth_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
depth_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
@@ -2062,6 +2067,63 @@ private:
|
||||
depth_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
depth_view.subresourceRange.levelCount = 1; depth_view.subresourceRange.layerCount = 1;
|
||||
check(vkCreateImageView(device_, &depth_view, nullptr, &depth_view_), "vkCreateImageView depth");
|
||||
if (samples_ != VK_SAMPLE_COUNT_1_BIT) create_msaa_color();
|
||||
}
|
||||
|
||||
// The multisampled colour target the subpass resolves into the swapchain image. It never
|
||||
// leaves the render pass, so it is transient (lazily allocated tile memory where available).
|
||||
void create_msaa_color() {
|
||||
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
info.imageType = VK_IMAGE_TYPE_2D;
|
||||
info.format = swapchain_format_;
|
||||
info.extent = {extent_.width, extent_.height, 1};
|
||||
info.mipLevels = 1; info.arrayLayers = 1;
|
||||
info.samples = samples_;
|
||||
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
check(vkCreateImage(device_, &info, nullptr, &msaa_image_), "vkCreateImage msaa color");
|
||||
VkMemoryRequirements reqs{};
|
||||
vkGetImageMemoryRequirements(device_, msaa_image_, &reqs);
|
||||
VkMemoryAllocateInfo alloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||||
alloc.allocationSize = reqs.size;
|
||||
try {
|
||||
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT);
|
||||
} catch (const std::runtime_error&) {
|
||||
alloc.memoryTypeIndex = find_memory_type(reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
}
|
||||
check(vkAllocateMemory(device_, &alloc, nullptr, &msaa_memory_), "vkAllocateMemory msaa color");
|
||||
check(vkBindImageMemory(device_, msaa_image_, msaa_memory_, 0), "vkBindImageMemory msaa color");
|
||||
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||||
view.image = msaa_image_; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = swapchain_format_;
|
||||
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
view.subresourceRange.levelCount = 1; view.subresourceRange.layerCount = 1;
|
||||
check(vkCreateImageView(device_, &view, nullptr, &msaa_view_), "vkCreateImageView msaa color");
|
||||
}
|
||||
|
||||
void destroy_msaa_color() {
|
||||
if (msaa_view_) { vkDestroyImageView(device_, msaa_view_, nullptr); msaa_view_ = VK_NULL_HANDLE; }
|
||||
if (msaa_image_) { vkDestroyImage(device_, msaa_image_, nullptr); msaa_image_ = VK_NULL_HANDLE; }
|
||||
if (msaa_memory_) { vkFreeMemory(device_, msaa_memory_, nullptr); msaa_memory_ = VK_NULL_HANDLE; }
|
||||
}
|
||||
|
||||
void create_framebuffers() {
|
||||
for (auto view : image_views_) {
|
||||
// Single-sample: {swapchain, depth}. MSAA: {msaa colour, depth, swapchain resolve}.
|
||||
VkImageView fb_attachments[3] = {view, depth_view_, VK_NULL_HANDLE};
|
||||
std::uint32_t count = 2;
|
||||
if (samples_ != VK_SAMPLE_COUNT_1_BIT) {
|
||||
fb_attachments[0] = msaa_view_;
|
||||
fb_attachments[2] = view;
|
||||
count = 3;
|
||||
}
|
||||
VkFramebufferCreateInfo framebuffer{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebuffer.renderPass = pass_; framebuffer.attachmentCount = count; framebuffer.pAttachments = fb_attachments;
|
||||
framebuffer.width = extent_.width; framebuffer.height = extent_.height; framebuffer.layers = 1;
|
||||
VkFramebuffer handle = VK_NULL_HANDLE;
|
||||
check(vkCreateFramebuffer(device_, &framebuffer, nullptr, &handle), "vkCreateFramebuffer");
|
||||
framebuffers_.push_back(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void create_host_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer, VkDeviceMemory& memory, void** mapped) {
|
||||
@@ -2292,20 +2354,29 @@ private:
|
||||
}
|
||||
|
||||
void create_render_pass_and_layout() {
|
||||
VkAttachmentDescription attachments[2]{};
|
||||
attachments[0].format = swapchain_format_; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
attachments[1].format = VK_FORMAT_D32_SFLOAT; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
const bool msaa = samples_ != VK_SAMPLE_COUNT_1_BIT;
|
||||
VkAttachmentDescription attachments[3]{};
|
||||
attachments[0].format = swapchain_format_; attachments[0].samples = samples_;
|
||||
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachments[0].storeOp = msaa ? VK_ATTACHMENT_STORE_OP_DONT_CARE : VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[0].finalLayout = msaa ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
// MSAA resolve target: the swapchain image.
|
||||
attachments[2].format = swapchain_format_; attachments[2].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[2].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[2].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[2].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[2].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||||
attachments[1].format = VK_FORMAT_D32_SFLOAT; attachments[1].samples = samples_;
|
||||
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
|
||||
VkAttachmentReference color_ref{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference depth_ref{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
||||
VkAttachmentReference resolve_ref{2, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
||||
VkSubpassDescription subpass{};
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &color_ref;
|
||||
if (msaa) subpass.pResolveAttachments = &resolve_ref;
|
||||
subpass.pDepthStencilAttachment = &depth_ref;
|
||||
|
||||
VkSubpassDependency dependency{};
|
||||
@@ -2315,20 +2386,11 @@ private:
|
||||
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
VkRenderPassCreateInfo pass_info{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||||
pass_info.attachmentCount = 2; pass_info.pAttachments = attachments;
|
||||
pass_info.attachmentCount = msaa ? 3 : 2; pass_info.pAttachments = attachments;
|
||||
pass_info.subpassCount = 1; pass_info.pSubpasses = &subpass;
|
||||
pass_info.dependencyCount = 1; pass_info.pDependencies = &dependency;
|
||||
check(vkCreateRenderPass(device_, &pass_info, nullptr, &pass_), "vkCreateRenderPass");
|
||||
|
||||
for (auto view : image_views_) {
|
||||
VkImageView fb_attachments[2] = {view, depth_view_};
|
||||
VkFramebufferCreateInfo framebuffer{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||||
framebuffer.renderPass = pass_; framebuffer.attachmentCount = 2; framebuffer.pAttachments = fb_attachments;
|
||||
framebuffer.width = extent_.width; framebuffer.height = extent_.height; framebuffer.layers = 1;
|
||||
VkFramebuffer handle = VK_NULL_HANDLE;
|
||||
check(vkCreateFramebuffer(device_, &framebuffer, nullptr, &handle), "vkCreateFramebuffer");
|
||||
framebuffers_.push_back(handle);
|
||||
}
|
||||
create_framebuffers();
|
||||
|
||||
const auto vert = read_spirv("native.vert.spv");
|
||||
const auto frag = read_spirv("native.frag.spv");
|
||||
@@ -2401,7 +2463,7 @@ private:
|
||||
raster.lineWidth = 1;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
|
||||
multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
multisample.rasterizationSamples = samples_;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depth_stencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
|
||||
depth_stencil.depthTestEnable = depth > 0 ? VK_TRUE : VK_FALSE;
|
||||
@@ -2986,6 +3048,10 @@ private:
|
||||
VkImage depth_image_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory depth_memory_ = VK_NULL_HANDLE;
|
||||
VkImageView depth_view_ = VK_NULL_HANDLE;
|
||||
VkSampleCountFlagBits samples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||
VkImage msaa_image_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory msaa_memory_ = VK_NULL_HANDLE;
|
||||
VkImageView msaa_view_ = VK_NULL_HANDLE;
|
||||
VkRenderPass pass_ = VK_NULL_HANDLE;
|
||||
std::vector<VkFramebuffer> framebuffers_;
|
||||
VkSampler sampler_ = VK_NULL_HANDLE;
|
||||
@@ -3081,7 +3147,7 @@ void print_summary(const VulkanWindow& renderer, int completed, double wall_ms,
|
||||
return frame_ms[std::min(index, frame_ms.size() - 1)];
|
||||
};
|
||||
std::cout << "device=" << renderer.device_name()
|
||||
<< " present_mode=" << renderer.present_mode_name()
|
||||
<< " present_mode=" << renderer.present_mode_name() << " msaa=" << renderer.msaa_samples()
|
||||
<< " frames=" << completed
|
||||
<< " draws=" << renderer.draw_count()
|
||||
<< " skinned_draws=" << renderer.skinned_draw_count()
|
||||
@@ -3175,6 +3241,15 @@ int run_live_client(
|
||||
game_port = server.GamePort();
|
||||
}
|
||||
|
||||
{
|
||||
// HiDPI text: rasterise glyph pages at the swapchain's pixels per UI pixel (before any font
|
||||
// exists). MT_FONT_OVERSAMPLE=1 keeps the 40250 bilevel 1x glyphs.
|
||||
const float density = float(renderer.width()) / float(std::max(1u, renderer.logical_width()));
|
||||
int oversample = std::clamp(int(std::lround(density)), 1, 4);
|
||||
if (const char* env = std::getenv("MT_FONT_OVERSAMPLE")) oversample = std::max(1, std::atoi(env));
|
||||
UISetFontOversample(oversample);
|
||||
}
|
||||
|
||||
std::string error;
|
||||
const char* env_stdlib = std::getenv("MT_PYTHON_STDLIB");
|
||||
std::string stdlib = (env_stdlib && *env_stdlib) ? env_stdlib : bundled_file_path("python27.zip");
|
||||
|
||||
@@ -105,7 +105,20 @@ void main() {
|
||||
return;
|
||||
}
|
||||
// 2D UI batch (UIRenderCommand): vertex color times texture, optionally clipped by a mask.
|
||||
vec4 color = in_color * texture(tex_sampler, in_uv);
|
||||
// The UI is laid out in logical pixels and magnified onto a HiDPI swapchain. Plain bilinear
|
||||
// smears each texel over the scale factor; "sharp bilinear" keeps texels square and only blends
|
||||
// across a one-pixel seam. Minified or 1:1 texels keep the original bilinear lookup.
|
||||
vec2 uv = in_uv;
|
||||
vec2 tex_size = vec2(textureSize(tex_sampler, 0));
|
||||
vec2 texel = uv * tex_size;
|
||||
vec2 texels_per_pixel = fwidth(texel);
|
||||
if (texels_per_pixel.x > 0.0 && texels_per_pixel.y > 0.0 &&
|
||||
texels_per_pixel.x < 0.99 && texels_per_pixel.y < 0.99) {
|
||||
vec2 seam = floor(texel + 0.5);
|
||||
texel = seam + clamp((texel - seam) / texels_per_pixel, -0.5, 0.5);
|
||||
uv = texel / tex_size;
|
||||
}
|
||||
vec4 color = in_color * texture(tex_sampler, uv);
|
||||
if (draw.params.z > 0.5) {
|
||||
if (in_mask_uv.x < 0.0 || in_mask_uv.x > 1.0 || in_mask_uv.y < 0.0 || in_mask_uv.y > 1.0)
|
||||
discard;
|
||||
|
||||
Reference in New Issue
Block a user