diff --git a/src/java.desktop/share/glsl/vulkan/clip.vert b/src/java.desktop/share/glsl/vulkan/clip.vert new file mode 100644 index 000000000000..abdc42dd8264 --- /dev/null +++ b/src/java.desktop/share/glsl/vulkan/clip.vert @@ -0,0 +1,11 @@ +#version 450 + +layout(push_constant) uniform PushConstants { + vec2 viewportNormalizer; // 2.0 / viewport +} push; + +layout(location = 0) in ivec2 in_Position; + +void main() { + gl_Position = vec4(vec2(in_Position) * push.viewportNormalizer - vec2(1.0), 0.0, 1.0); +} \ No newline at end of file diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKImage.c b/src/java.desktop/share/native/common/java2d/vulkan/VKImage.c index 75d337f07a9a..0f3efca33330 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKImage.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKImage.c @@ -40,7 +40,7 @@ static VkBool32 VKImage_CreateView(VKDevice* device, VKImage* image) { .image = image->handle, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = image->format, - .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .subresourceRange.aspectMask = VKImage_GetAspect(image), .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, @@ -53,6 +53,11 @@ static VkBool32 VKImage_CreateView(VKDevice* device, VKImage* image) { return VK_TRUE; } +VkImageAspectFlagBits VKImage_GetAspect(VKImage* image) { + return VKUtil_GetFormatGroup(image->format).bytes == 0 ? // Unknown format group means stencil. + VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_COLOR_BIT; +} + VKImage* VKImage_Create(VKDevice* device, uint32_t width, uint32_t height, VkImageCreateFlags flags, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkSampleCountFlagBits samples, diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKImage.h b/src/java.desktop/share/native/common/java2d/vulkan/VKImage.h index a6fd922a2093..43a4cda9509a 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKImage.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKImage.h @@ -42,6 +42,8 @@ struct VKImage { VkAccessFlagBits lastAccess; }; +VkImageAspectFlagBits VKImage_GetAspect(VKImage* image); + VKImage* VKImage_Create(VKDevice* device, uint32_t width, uint32_t height, VkImageCreateFlags flags, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkSampleCountFlagBits samples, diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.c b/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.c index 5a3be10055f8..4e62f873c718 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.c @@ -109,6 +109,7 @@ for (uint32_t i = 0; i < SARRAY_COUNT_OF(INPUT_STATE_ATTRIBUTES_##NAME); i++) { typedef struct { VkGraphicsPipelineCreateInfo createInfo; VkPipelineMultisampleStateCreateInfo multisampleState; + VkPipelineDepthStencilStateCreateInfo depthStencilState;; VkPipelineColorBlendStateCreateInfo colorBlendState; VkPipelineDynamicStateCreateInfo dynamicState; VkDynamicState dynamicStates[2]; @@ -148,6 +149,20 @@ static void VKPipelines_InitPipelineCreateState(PipelineCreateState* state) { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT }; + const VkStencilOpState stencilOpState = { + .failOp = VK_STENCIL_OP_KEEP, + .passOp = VK_STENCIL_OP_KEEP, + .compareOp = VK_COMPARE_OP_NOT_EQUAL, + .compareMask = 0xFFFFFFFFU, + .writeMask = 0U, + .reference = CLIP_STENCIL_EXCLUDE_VALUE + }; + state->depthStencilState = (VkPipelineDepthStencilStateCreateInfo) { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .stencilTestEnable = VK_FALSE, + .front = stencilOpState, + .back = stencilOpState + }; state->colorBlendState = (VkPipelineColorBlendStateCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, @@ -168,6 +183,7 @@ static void VKPipelines_InitPipelineCreateState(PipelineCreateState* state) { .pViewportState = &viewportState, .pRasterizationState = &rasterizationState, .pMultisampleState = &state->multisampleState, + .pDepthStencilState = &state->depthStencilState, .pColorBlendState = &state->colorBlendState, .pDynamicState = &state->dynamicState, .subpass = 0, @@ -245,9 +261,10 @@ static VKPipelineSet* VKPipelines_CreatePipelineSet(VKRenderPassContext* renderP PipelineCreateState base; VKPipelines_InitPipelineCreateState(&base); base.createInfo.layout = pipelineContext->pipelineLayout; - base.createInfo.renderPass = renderPassContext->renderPass; + base.createInfo.renderPass = renderPassContext->renderPass[descriptor.stencilMode != STENCIL_MODE_NONE]; base.colorBlendState.pAttachments = &COMPOSITE_BLEND_STATES[descriptor.composite]; if (COMPOSITE_GROUP(descriptor.composite) == LOGIC_COMPOSITE_GROUP) base.colorBlendState.logicOpEnable = VK_TRUE; + if (descriptor.stencilMode == STENCIL_MODE_ON) base.depthStencilState.stencilTestEnable = VK_TRUE; assert(base.dynamicState.dynamicStateCount <= SARRAY_COUNT_OF(base.dynamicStates)); ShaderStages stages[PIPELINE_COUNT]; @@ -282,13 +299,14 @@ static VKPipelineSet* VKPipelines_CreatePipelineSet(VKRenderPassContext* renderP // TODO pipeline cache VK_IF_ERROR(device->vkCreateGraphicsPipelines(device->handle, VK_NULL_HANDLE, PIPELINE_COUNT, createInfos, NULL, set->pipelines)) VK_UNHANDLED_ERROR(); - J2dRlsTraceLn1(J2D_TRACE_INFO, "VKPipelines_CreatePipelineSet: composite=%d", descriptor.composite); + J2dRlsTraceLn2(J2D_TRACE_INFO, "VKPipelines_CreatePipelineSet: composite=%d, stencilMode=%d", + descriptor.composite, descriptor.stencilMode); return set; } -static VkResult VKPipelines_InitRenderPass(VKDevice* device, VKRenderPassContext* renderPassContext) { +static VkResult VKPipelines_InitRenderPasses(VKDevice* device, VKRenderPassContext* renderPassContext) { assert(device != NULL && renderPassContext != NULL); - VkAttachmentDescription colorAttachment = { + VkAttachmentDescription attachments[] = {{ .format = renderPassContext->format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, @@ -297,36 +315,47 @@ static VkResult VKPipelines_InitRenderPass(VKDevice* device, VKRenderPassContext .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL - }; + }, { + .format = VK_FORMAT_S8_UINT, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, + .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL + }}; VkAttachmentReference colorReference = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; + VkAttachmentReference stencilReference = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL + }; VkSubpassDescription subpassDescription = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorReference }; - // TODO this is probably not needed? -// // Subpass dependencies for layout transitions -// VkSubpassDependency dependency = { -// .srcSubpass = VK_SUBPASS_EXTERNAL, -// .dstSubpass = 0, -// .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, -// .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, -// .srcAccessMask = 0, -// .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT -// }; VkRenderPassCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, - .pAttachments = &colorAttachment, + .pAttachments = attachments, .subpassCount = 1, .pSubpasses = &subpassDescription, .dependencyCount = 0, .pDependencies = NULL }; - return device->vkCreateRenderPass(device->handle, &createInfo, NULL, &renderPassContext->renderPass); + for (uint32_t i = 0; i < 2; i++) { + if (i == 1) { + createInfo.attachmentCount = 2; + subpassDescription.pDepthStencilAttachment = &stencilReference; + } + VkResult result = device->vkCreateRenderPass(device->handle, &createInfo, NULL, &renderPassContext->renderPass[i]); + VK_IF_ERROR(result) return result; + } + return VK_SUCCESS; } static void VKPipelines_DestroyRenderPassContext(VKRenderPassContext* renderPassContext) { @@ -342,7 +371,10 @@ static void VKPipelines_DestroyRenderPassContext(VKRenderPassContext* renderPass } } ARRAY_FREE(renderPassContext->pipelineSets); - device->vkDestroyRenderPass(device->handle, renderPassContext->renderPass, NULL); + device->vkDestroyPipeline(device->handle, renderPassContext->clipPipeline, NULL); + for (uint32_t i = 0; i < 2; i++) { + device->vkDestroyRenderPass(device->handle, renderPassContext->renderPass[i], NULL); + } J2dRlsTraceLn2(J2D_TRACE_INFO, "VKPipelines_DestroyRenderPassContext(%p): format=%d", renderPassContext, renderPassContext->format); free(renderPassContext); @@ -355,11 +387,50 @@ static VKRenderPassContext* VKPipelines_CreateRenderPassContext(VKPipelineContex renderPassContext->pipelineContext = pipelineContext; renderPassContext->format = format; - VK_IF_ERROR(VKPipelines_InitRenderPass(pipelineContext->device, renderPassContext)) { + VK_IF_ERROR(VKPipelines_InitRenderPasses(pipelineContext->device, renderPassContext)) { VKPipelines_DestroyRenderPassContext(renderPassContext); return NULL; } + // Setup default pipeline parameters. + const VkPipelineColorBlendAttachmentState NO_COLOR_ATTACHMENT = { + .blendEnable = VK_FALSE, + .colorWriteMask = 0 + }; + PipelineCreateState base; + VKPipelines_InitPipelineCreateState(&base); + base.createInfo.layout = pipelineContext->pipelineLayout; + base.createInfo.renderPass = renderPassContext->renderPass[1]; + base.colorBlendState.pAttachments = &NO_COLOR_ATTACHMENT; + base.depthStencilState.stencilTestEnable = VK_TRUE; + assert(base.dynamicState.dynamicStateCount <= SARRAY_COUNT_OF(base.dynamicStates)); + + // Setup clip pipeline. + MAKE_INPUT_STATE(CLIP, VKIntVertex, VK_FORMAT_R32G32_SINT); + base.createInfo.pVertexInputState = &INPUT_STATE_CLIP; + base.createInfo.pInputAssemblyState = &INPUT_ASSEMBLY_STATE_TRIANGLE_LIST; + const VkStencilOpState CLIP_STENCIL_OP = { + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_NEVER, + .compareMask = 0U, + .writeMask = 0xFFFFFFFFU, + .reference = CLIP_STENCIL_INCLUDE_VALUE + }; + const VkPipelineDepthStencilStateCreateInfo CLIP_STENCIL_STATE = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .stencilTestEnable = VK_TRUE, + .front = CLIP_STENCIL_OP, + .back = CLIP_STENCIL_OP + }; + base.createInfo.pDepthStencilState = &CLIP_STENCIL_STATE; + base.createInfo.stageCount = 1; + base.createInfo.pStages = &pipelineContext->shaders->clip_vert; + + // Create pipelines. + // TODO pipeline cache + VK_IF_ERROR(pipelineContext->device->vkCreateGraphicsPipelines( + pipelineContext->device->handle, VK_NULL_HANDLE, 1, &base.createInfo, NULL, &renderPassContext->clipPipeline)) VK_UNHANDLED_ERROR(); J2dRlsTraceLn2(J2D_TRACE_INFO, "VKPipelines_CreateRenderPassContext(%p): format=%d", renderPassContext, format); return renderPassContext; } @@ -504,11 +575,13 @@ inline void hash(uint32_t* result, int i) { // Good for hashing enums. } inline uint32_t pipelineSetDescriptorHash(VKPipelineSetDescriptor* d) { uint32_t h = 0U; + hash(&h, d->stencilMode); hash(&h, d->composite); return h; } inline VkBool32 pipelineSetDescriptorEquals(VKPipelineSetDescriptor* a, VKPipelineSetDescriptor* b) { - return a->composite == b->composite; + return a->stencilMode == b->stencilMode && + a->composite == b->composite; } VkPipeline VKPipelines_GetPipeline(VKRenderPassContext* renderPassContext, VKPipelineSetDescriptor descriptor, VKPipeline pipeline) { diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.h b/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.h index 17e828791cd1..8b7f545a6d62 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKPipelines.h @@ -27,6 +27,9 @@ #include "java_awt_AlphaComposite.h" #include "VKTypes.h" +#define CLIP_STENCIL_INCLUDE_VALUE 0x80U +#define CLIP_STENCIL_EXCLUDE_VALUE 0U + /** * All pipeline types. */ @@ -39,6 +42,12 @@ typedef enum { NO_PIPELINE = 0x7FFFFFFF } VKPipeline; +typedef enum { + STENCIL_MODE_NONE = 0, // No stencil attachment. + STENCIL_MODE_OFF = 1, // Has stencil attachment, stencil test disabled. + STENCIL_MODE_ON = 2 // Has stencil attachment, stencil test enabled. +} VKStencilMode; + /** * There are two groups of composite modes: * - Logic composite - using logicOp. @@ -93,7 +102,8 @@ struct VKPipelineContext { struct VKRenderPassContext { VKPipelineContext* pipelineContext; VkFormat format; - VkRenderPass renderPass; + VkRenderPass renderPass[2]; // Color-only and color+stencil. + VkPipeline clipPipeline; struct VKPipelineSet** pipelineSets; }; @@ -102,9 +112,14 @@ struct VKRenderPassContext { * When adding new fields, update hash and comparison in VKPipelines_GetPipeline. */ typedef struct { + VKStencilMode stencilMode; VKCompositeMode composite; } VKPipelineSetDescriptor; +typedef struct { + int x, y; +} VKIntVertex; + typedef struct { float x, y; Color color; diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c index 9e9bd0255277..fddf193a3fa4 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderQueue.c @@ -93,15 +93,19 @@ #define OFFSET_XFORM sun_java2d_vulkan_VKBlitLoops_OFFSET_XFORM #define OFFSET_ISOBLIT sun_java2d_vulkan_VKBlitLoops_OFFSET_ISOBLIT +#define NO_CLIP ((VkRect2D) {{0, 0}, {0x7FFFFFFFU, 0x7FFFFFFFU}}) + // Rendering context is only accessed from VKRenderQueue_flushBuffer, // which is only called from queue flusher thread, no need for synchronization. static VKRenderingContext context = { .surface = NULL, .transform = {1.0, 0.0, 0.0,0.0, 1.0, 0.0}, - .clipRect = {{0, 0},{INT_MAX, INT_MAX}}, .color = {}, .composite = ALPHA_COMPOSITE_SRC_OVER, - .extraAlpha = 1.0f + .extraAlpha = 1.0f, + .clipModCount = 1, + .clipRect = NO_CLIP, + .clipSpanVertices = NULL }; // We keep this color separately from context.color, // because we need consistent state when switching between XOR and alpha composite modes. @@ -435,12 +439,17 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer J2dRlsTraceLn4(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: SET_RECT_CLIP(%d, %d, %d, %d)", x1, y1, x2, y2); + ARRAY_RESIZE(context.clipSpanVertices, 0); + context.clipRect = (VkRect2D) {{x1, y1}, {x2 - x1, y2 - y1}}; + context.clipModCount++; } break; case sun_java2d_pipe_BufferedOpCodes_BEGIN_SHAPE_CLIP: { J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: BEGIN_SHAPE_CLIP"); + ARRAY_RESIZE(context.clipSpanVertices, 0); + context.clipModCount++; } break; case sun_java2d_pipe_BufferedOpCodes_SET_SHAPE_CLIP_SPANS: @@ -448,19 +457,38 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer jint count = NEXT_INT(b); J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: SET_SHAPE_CLIP_SPANS"); - SKIP_BYTES(b, count * BYTES_PER_SPAN); + size_t offset = ARRAY_SIZE(context.clipSpanVertices); + ARRAY_RESIZE(context.clipSpanVertices, offset + count * 6); + for (jint i = 0; i < count; i++) { + jint x1 = NEXT_INT(b); + jint y1 = NEXT_INT(b); + jint x2 = NEXT_INT(b); + jint y2 = NEXT_INT(b); + context.clipSpanVertices[offset + i * 6 + 0] = (VKIntVertex) {x1, y1}; + context.clipSpanVertices[offset + i * 6 + 1] = (VKIntVertex) {x2, y1}; + context.clipSpanVertices[offset + i * 6 + 2] = (VKIntVertex) {x2, y2}; + context.clipSpanVertices[offset + i * 6 + 3] = (VKIntVertex) {x2, y2}; + context.clipSpanVertices[offset + i * 6 + 4] = (VKIntVertex) {x1, y2}; + context.clipSpanVertices[offset + i * 6 + 5] = (VKIntVertex) {x1, y1}; + } + context.clipModCount++; } break; case sun_java2d_pipe_BufferedOpCodes_END_SHAPE_CLIP: { J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: END_SHAPE_CLIP"); + context.clipRect = NO_CLIP; + context.clipModCount++; } break; case sun_java2d_pipe_BufferedOpCodes_RESET_CLIP: { J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: RESET_CLIP"); + ARRAY_RESIZE(context.clipSpanVertices, 0); + context.clipRect = NO_CLIP; + context.clipModCount++; } break; case sun_java2d_pipe_BufferedOpCodes_SET_ALPHA_COMPOSITE: diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.c b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.c index 5cb210495ca1..9293fa61f4bf 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.c @@ -139,6 +139,7 @@ struct VKRenderPass { VKRenderPassContext* context; VKBuffer* vertexBuffers; VKTexelBuffer* maskFillBuffers; + VkRenderPass renderPass; // Non-owning. VkFramebuffer framebuffer; VkCommandBuffer commandBuffer; @@ -149,6 +150,8 @@ struct VKRenderPass { VKCompositeMode currentComposite; VKPipeline currentPipeline; + VKStencilMode currentStencilMode; + uint64_t clipModCount; // Just a tag to detect when clip was changed. VkBool32 pendingFlush; VkBool32 pendingCommands; VkBool32 pendingClear; @@ -478,7 +481,7 @@ void VKRenderer_AddImageBarrier(VkImageMemoryBarrier* barriers, VKBarrierBatch* .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image->handle, - .subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } + .subresourceRange = { VKImage_GetAspect(image), 0, 1, 0, 1 } }; batch->barrierCount++; batch->srcStages |= image->lastStage; @@ -584,10 +587,13 @@ static VkBool32 VKRenderer_InitRenderPass(VKSDOps* surface) { VKRenderPass* renderPass = surface->renderPass = malloc(sizeof(VKRenderPass)); VK_RUNTIME_ASSERT(renderPass); (*renderPass) = (VKRenderPass) { - .pendingCommands = VK_FALSE, - .pendingClear = VK_TRUE, // Clear the surface by default .currentComposite = NO_COMPOSITE, .currentPipeline = NO_PIPELINE, + .currentStencilMode = STENCIL_MODE_NONE, + .clipModCount = 0, + .pendingFlush = VK_FALSE, + .pendingCommands = VK_FALSE, + .pendingClear = VK_TRUE, // Clear the surface by default .lastTimestamp = 0 }; @@ -596,23 +602,48 @@ static VkBool32 VKRenderer_InitRenderPass(VKSDOps* surface) { renderPass->context = VKPipelines_GetRenderPassContext(renderer->pipelineContext, surface->image->format); } + J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitRenderPass(%p)", surface); + return VK_TRUE; +} + +/** + * Initialize surface framebuffer. + * This function can be called between render passes of a single frame, unlike VKRenderer_InitRenderPass. + */ +static void VKRenderer_InitFramebuffer(VKSDOps* surface) { + assert(surface != NULL && surface->device != NULL && surface->renderPass != NULL); + VKDevice* device = surface->device; + VKRenderPass* renderPass = surface->renderPass; + + if (renderPass->currentStencilMode == STENCIL_MODE_NONE && surface->stencil != NULL) { + // Destroy outdated color-only framebuffer. + device->vkDestroyFramebuffer(device->handle, renderPass->framebuffer, NULL); + renderPass->framebuffer = VK_NULL_HANDLE; + renderPass->currentStencilMode = STENCIL_MODE_OFF; + } + // Initialize framebuffer. if (renderPass->framebuffer == VK_NULL_HANDLE) { + renderPass->renderPass = renderPass->context->renderPass[surface->stencil != NULL]; + VkImageView views[] = { surface->image->view, VK_NULL_HANDLE }; VkFramebufferCreateInfo framebufferCreateInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .renderPass = renderPass->context->renderPass, + .renderPass = renderPass->renderPass, .attachmentCount = 1, - .pAttachments = &surface->image->view, + .pAttachments = views, .width = surface->image->extent.width, .height = surface->image->extent.height, .layers = 1 }; + if (surface->stencil != NULL) { + framebufferCreateInfo.attachmentCount = 2; + views[1] = surface->stencil->view; + } VK_IF_ERROR(device->vkCreateFramebuffer(device->handle, &framebufferCreateInfo, NULL, &renderPass->framebuffer)) VK_UNHANDLED_ERROR(); - } - J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitRenderPass(%p)", surface); - return VK_TRUE; + J2dRlsTraceLn1(J2D_TRACE_VERBOSE, "VKRenderer_InitFramebuffer(%p)", surface); + } } /** @@ -620,6 +651,7 @@ static VkBool32 VKRenderer_InitRenderPass(VKSDOps* surface) { */ static void VKRenderer_BeginRenderPass(VKSDOps* surface) { assert(surface != NULL && surface->renderPass != NULL && !surface->renderPass->pendingCommands); + VKRenderer_InitFramebuffer(surface); // We may have a pending flush, which is already obsolete. surface->renderPass->pendingFlush = VK_FALSE; VKDevice* device = surface->device; @@ -646,7 +678,7 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) { // Begin recording render pass commands. VkCommandBufferInheritanceInfo inheritanceInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, - .renderPass = surface->renderPass->context->renderPass, + .renderPass = surface->renderPass->renderPass, .subpass = 0, .framebuffer = surface->renderPass->framebuffer }; @@ -675,7 +707,7 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) { surface->renderPass->pendingClear = VK_FALSE; } - // Set viewport and scissor. + // Set viewport. VkViewport viewport = { .x = 0.0f, .y = 0.0f, @@ -684,9 +716,7 @@ static void VKRenderer_BeginRenderPass(VKSDOps* surface) { .minDepth = 0.0f, .maxDepth = 1.0f }; - VkRect2D scissor = {{0, 0}, surface->image->extent}; device->vkCmdSetViewport(commandBuffer, 0, 1, &viewport); - device->vkCmdSetScissor(commandBuffer, 0, 1, &scissor); // Calculate inverse viewport for vertex shader. viewport.width = 2.0f / viewport.width; viewport.height = 2.0f / viewport.height; @@ -717,22 +747,30 @@ void VKRenderer_FlushRenderPass(VKSDOps* surface) { surface->renderPass->lastTimestamp = renderer->writeTimestamp; VkCommandBuffer cb = VKRenderer_Record(renderer); - // Insert barrier to prepare surface for rendering. - VkImageMemoryBarrier barriers[1]; + // Insert barriers to prepare surface for rendering. + VkImageMemoryBarrier barriers[2]; VKBarrierBatch barrierBatch = {}; VKRenderer_AddImageBarrier(barriers, &barrierBatch, surface->image, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + if (surface->stencil != NULL) { + VKRenderer_AddImageBarrier(barriers, &barrierBatch, surface->stencil, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + } if (barrierBatch.barrierCount > 0) { device->vkCmdPipelineBarrier(cb, barrierBatch.srcStages, barrierBatch.dstStages, 0, 0, NULL, 0, NULL, barrierBatch.barrierCount, barriers); } + // If there is a pending clear, record it into render pass. + if (clear) VKRenderer_BeginRenderPass(surface); // Begin render pass. VkRenderPassBeginInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .renderPass = surface->renderPass->context->renderPass, + .renderPass = surface->renderPass->renderPass, .framebuffer = surface->renderPass->framebuffer, .renderArea.offset = (VkOffset2D){0, 0}, .renderArea.extent = surface->image->extent, @@ -740,8 +778,6 @@ void VKRenderer_FlushRenderPass(VKSDOps* surface) { .pClearValues = NULL }; device->vkCmdBeginRenderPass(cb, &renderPassInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); - // If there is a pending clear, record it into render pass. - if (clear) VKRenderer_BeginRenderPass(surface); // Execute render pass commands. if (surface->renderPass->pendingCommands) { @@ -961,6 +997,54 @@ static BufferWritingState VKRenderer_AllocateMaskFillBytes(VKRenderingContext* c return state; } +/** + * Setup stencil attachment according to the context clip state. + * If there is a clip shape, attachment is cleared with "fail" value and then + * pixels inside the clip shape are set to "pass". + * If there is no clip shape, whole attachment is cleared with "pass" value. + */ +static void VKRenderer_SetupStencil(VKRenderingContext* context) { + assert(context != NULL && context->surface != NULL && context->surface->renderPass != NULL); + VKSDOps* surface = context->surface; + VKRenderPass* renderPass = surface->renderPass; + VkCommandBuffer cb = renderPass->commandBuffer; + VKRenderer_FlushDraw(surface); + + // Clear stencil attachment. + VkClearAttachment clearAttachment = { + .aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT, + .clearValue.depthStencil.stencil = ARRAY_SIZE(context->clipSpanVertices) > 0 ? + CLIP_STENCIL_EXCLUDE_VALUE : CLIP_STENCIL_INCLUDE_VALUE + }; + VkClearRect clearRect = { + .rect = {{0, 0}, surface->stencil->extent}, + .baseArrayLayer = 0, + .layerCount = 1 + }; + surface->device->vkCmdClearAttachments(cb, 1, &clearAttachment, 1, &clearRect); + + // Bind the clip pipeline. + surface->device->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, surface->renderPass->context->clipPipeline); + // Reset vertex buffer binding. + renderPass->vertexBufferWriting.bound = VK_FALSE; + + // Rasterize clip spans. + const uint32_t MAX_VERTICES_PER_DRAW = (VERTEX_BUFFER_SIZE / sizeof(VKIntVertex) / 3) * 3; + VKIntVertex* vs; + for (uint32_t drawn = 0;;) { + uint32_t currentDraw = ARRAY_SIZE(context->clipSpanVertices) - drawn; + if (currentDraw > MAX_VERTICES_PER_DRAW) currentDraw = MAX_VERTICES_PER_DRAW; + else if (currentDraw == 0) break; + VK_DRAW(vs, context, currentDraw); + memcpy(vs, context->clipSpanVertices + drawn, currentDraw * sizeof(VKIntVertex)); + drawn += currentDraw; + } + VKRenderer_FlushDraw(surface); + + // Reset pipeline state. + renderPass->currentPipeline = NO_PIPELINE; +} + /** * Setup pipeline for drawing. Returns FALSE if surface is not yet ready for drawing. */ @@ -977,19 +1061,41 @@ VkBool32 VKRenderer_Validate(VKRenderingContext* context, VKPipeline pipeline) { VKRenderPass* renderPass = surface->renderPass; // Validate render pass state. - if (renderPass->currentComposite != context->composite) { + if (renderPass->currentComposite != context->composite || + renderPass->clipModCount != context->clipModCount) { // ALPHA_COMPOSITE_DST keeps destination intact, so don't even bother to change the state. if (context->composite == ALPHA_COMPOSITE_DST) return VK_FALSE; VKCompositeMode oldComposite = renderPass->currentComposite; + VkBool32 clipChanged = renderPass->clipModCount != context->clipModCount; + // Init stencil attachment, if needed. + if (clipChanged && ARRAY_SIZE(context->clipSpanVertices) > 0 && surface->stencil == NULL) { + if (surface->renderPass->pendingCommands) VKRenderer_FlushRenderPass(surface); + if (!VKSD_ConfigureImageSurfaceStencil(surface)) return VK_FALSE; + } // Update state. VKRenderer_FlushDraw(surface); renderPass->currentComposite = context->composite; + renderPass->clipModCount = context->clipModCount; // Begin render pass. - if (!renderPass->pendingCommands) VKRenderer_BeginRenderPass(surface); + VkBool32 renderPassJustStarted = !renderPass->pendingCommands; + if (renderPassJustStarted) VKRenderer_BeginRenderPass(surface); + // Validate current clip. + if (clipChanged || renderPassJustStarted) { + J2dTraceLn(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating clip"); + surface->device->vkCmdSetScissor(renderPass->commandBuffer, 0, 1, &context->clipRect); + if (clipChanged) { + if (ARRAY_SIZE(context->clipSpanVertices) > 0) { + VKRenderer_SetupStencil(context); + renderPass->currentStencilMode = STENCIL_MODE_ON; + } else renderPass->currentStencilMode = surface->stencil != NULL ? STENCIL_MODE_OFF : STENCIL_MODE_NONE; + } + } // Validate current composite. - J2dTraceLn2(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating composite, old=%d, new=%d", oldComposite, context->composite); - // Reset the pipeline. - renderPass->currentPipeline = NO_PIPELINE; + if (oldComposite != context->composite) { + J2dTraceLn2(J2D_TRACE_VERBOSE, "VKRenderer_Validate: updating composite, old=%d, new=%d", oldComposite, context->composite); + // Reset the pipeline. + renderPass->currentPipeline = NO_PIPELINE; + } } // Validate current pipeline. @@ -1000,7 +1106,8 @@ VkBool32 VKRenderer_Validate(VKRenderingContext* context, VKPipeline pipeline) { VkCommandBuffer cb = renderPass->commandBuffer; renderPass->currentPipeline = pipeline; VKPipelineSetDescriptor pipelineSetDescriptor = { - .composite = context->composite + .stencilMode = renderPass->currentStencilMode, + .composite = renderPass->currentComposite }; surface->device->vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, VKPipelines_GetPipeline(renderPass->context, pipelineSetDescriptor, pipeline)); diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.h b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.h index f5f33538fe62..846c4ebb63c1 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKRenderer.h @@ -33,12 +33,14 @@ struct VKRenderingContext { VKSDOps* surface; VKTransform transform; - VkRect2D clipRect; Color color; VKCompositeMode composite; // Extra alpha is not used when painting with plain color, // in this case color.a already includes it. float extraAlpha; + uint64_t clipModCount; // Used to track changes to the clip. + VkRect2D clipRect; + VKIntVertex* clipSpanVertices; }; typedef struct { diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.c b/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.c index 277544645b3a..57cc50ead9a6 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.c +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.c @@ -40,9 +40,10 @@ static void VKSD_ResetImageSurface(VKSDOps* vksdo) { // DestroyRenderPass also waits while the surface resources are being used by device. VKRenderer_DestroyRenderPass(vksdo); - if (vksdo->device != NULL && vksdo->image != NULL) { + if (vksdo->device != NULL) { + VKImage_Destroy(vksdo->device, vksdo->stencil); VKImage_Destroy(vksdo->device, vksdo->image); - vksdo->image = NULL; + vksdo->image = vksdo->stencil = NULL; } } @@ -100,6 +101,25 @@ VkBool32 VKSD_ConfigureImageSurface(VKSDOps* vksdo) { return vksdo->image != NULL; } +VkBool32 VKSD_ConfigureImageSurfaceStencil(VKSDOps* vksdo) { + // Check that image is ready. + if (vksdo->image == NULL) { + J2dRlsTraceLn1(J2D_TRACE_WARNING, "VKSD_ConfigureImageSurfaceStencil(%p): image is not ready", vksdo); + return VK_FALSE; + } + // Initialize stencil image. + if (vksdo->stencil == NULL) { + vksdo->stencil = VKImage_Create(vksdo->device, vksdo->image->extent.width, vksdo->image->extent.height, + 0, VK_FORMAT_S8_UINT, VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_SAMPLE_COUNT_1_BIT, VKSD_FindImageSurfaceMemoryType); + VK_RUNTIME_ASSERT(vksdo->stencil); + J2dRlsTraceLn3(J2D_TRACE_INFO, "VKSD_ConfigureImageSurfaceStencil(%p): stencil image updated %dx%d", + vksdo, vksdo->stencil->extent.width, vksdo->stencil->extent.height); + } + return vksdo->stencil != NULL; +} + VkBool32 VKSD_ConfigureWindowSurface(VKWinSDOps* vkwinsdo) { // Check that image is ready. if (vkwinsdo->vksdOps.image == NULL) { diff --git a/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.h b/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.h index ce88d73295d1..beb17b5825fe 100644 --- a/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.h +++ b/src/java.desktop/share/native/common/java2d/vulkan/VKSurfaceData.h @@ -50,6 +50,7 @@ struct VKSDOps { jint drawableType; VKDevice* device; VKImage* image; + VKImage* stencil; Color background; VkExtent2D requestedExtent; @@ -84,6 +85,12 @@ void VKSD_ResetSurface(VKSDOps* vksdo); */ VkBool32 VKSD_ConfigureImageSurface(VKSDOps* vksdo); +/** + * [Re]configure stencil attachment of the image surface. + * VKSD_ConfigureImageSurface must have been called before. + */ +VkBool32 VKSD_ConfigureImageSurfaceStencil(VKSDOps* vksdo); + /** * Configure window surface. This [re]initializes the swapchain. * VKSD_ConfigureImageSurface must have been called before.