1
0
mirror of https://github.com/EduApps-CDG/OpenDX synced 2024-12-30 09:45:37 +01:00

[dxvk] Support image sub-regions for resolve operations

Required for legacy graphics APIs.
This commit is contained in:
Philip Rebohle 2019-04-19 11:19:14 +02:00
parent a3966b442b
commit 95bfac84f1
No known key found for this signature in database
GPG Key ID: C8CC613427A31C99
10 changed files with 113 additions and 75 deletions

View File

@ -1321,10 +1321,14 @@ namespace dxvk {
cSrcSubres = srcSubresourceLayers, cSrcSubres = srcSubresourceLayers,
cFormat = format cFormat = format
] (DxvkContext* ctx) { ] (DxvkContext* ctx) {
ctx->resolveImage( VkImageResolve region;
cDstImage, cDstSubres, region.srcSubresource = cSrcSubres;
cSrcImage, cSrcSubres, region.srcOffset = VkOffset3D { 0, 0, 0 };
cFormat); region.dstSubresource = cDstSubres;
region.dstOffset = VkOffset3D { 0, 0, 0 };
region.extent = cDstImage->mipLevelExtent(cDstSubres.mipLevel);
ctx->resolveImage(cDstImage, cSrcImage, region, cFormat);
}); });
} }
} }

View File

@ -213,16 +213,22 @@ namespace dxvk {
// Resolve back buffer if it is multisampled. We // Resolve back buffer if it is multisampled. We
// only have to do it only for the first frame. // only have to do it only for the first frame.
if (m_swapImageResolve != nullptr && i == 0) { if (m_swapImageResolve != nullptr && i == 0) {
VkImageSubresourceLayers resolveSubresources; VkImageSubresourceLayers resolveSubresource;
resolveSubresources.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; resolveSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
resolveSubresources.mipLevel = 0; resolveSubresource.mipLevel = 0;
resolveSubresources.baseArrayLayer = 0; resolveSubresource.baseArrayLayer = 0;
resolveSubresources.layerCount = 1; resolveSubresource.layerCount = 1;
VkImageResolve resolveRegion;
resolveRegion.srcSubresource = resolveSubresource;
resolveRegion.srcOffset = VkOffset3D { 0, 0, 0 };
resolveRegion.dstSubresource = resolveSubresource;
resolveRegion.dstOffset = VkOffset3D { 0, 0, 0 };
resolveRegion.extent = m_swapImage->info().extent;
m_context->resolveImage( m_context->resolveImage(
m_swapImageResolve, resolveSubresources, m_swapImageResolve, m_swapImage,
m_swapImage, resolveSubresources, resolveRegion, VK_FORMAT_UNDEFINED);
VK_FORMAT_UNDEFINED);
} }
// Presentation semaphores and WSI swap chain image // Presentation semaphores and WSI swap chain image

View File

@ -1671,9 +1671,8 @@ namespace dxvk {
void DxvkContext::resolveImage( void DxvkContext::resolveImage(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources, const VkImageResolve& region,
VkFormat format) { VkFormat format) {
this->spillRenderPass(); this->spillRenderPass();
@ -1683,13 +1682,10 @@ namespace dxvk {
if (srcImage->info().format == format if (srcImage->info().format == format
&& dstImage->info().format == format) { && dstImage->info().format == format) {
this->resolveImageHw( this->resolveImageHw(
dstImage, dstSubresources, dstImage, srcImage, region);
srcImage, srcSubresources);
} else { } else {
this->resolveImageFb( this->resolveImageFb(
dstImage, dstSubresources, dstImage, srcImage, region, format);
srcImage, srcSubresources,
format);
} }
} }
@ -2605,11 +2601,10 @@ namespace dxvk {
void DxvkContext::resolveImageHw( void DxvkContext::resolveImageHw(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources) { const VkImageResolve& region) {
auto dstSubresourceRange = vk::makeSubresourceRange(dstSubresources); auto dstSubresourceRange = vk::makeSubresourceRange(region.dstSubresource);
auto srcSubresourceRange = vk::makeSubresourceRange(srcSubresources); auto srcSubresourceRange = vk::makeSubresourceRange(region.srcSubresource);
if (m_barriers.isImageDirty(dstImage, dstSubresourceRange, DxvkAccess::Write) if (m_barriers.isImageDirty(dstImage, dstSubresourceRange, DxvkAccess::Write)
|| m_barriers.isImageDirty(srcImage, srcSubresourceRange, DxvkAccess::Write)) || m_barriers.isImageDirty(srcImage, srcSubresourceRange, DxvkAccess::Write))
@ -2617,9 +2612,14 @@ namespace dxvk {
// We only support resolving to the entire image // We only support resolving to the entire image
// area, so we might as well discard its contents // area, so we might as well discard its contents
VkImageLayout initialLayout = dstImage->info().layout;
if (dstImage->isFullSubresource(region.dstSubresource, region.extent))
initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
m_transitions.accessImage( m_transitions.accessImage(
dstImage, dstSubresourceRange, dstImage, dstSubresourceRange,
VK_IMAGE_LAYOUT_UNDEFINED, 0, 0, initialLayout, 0, 0,
dstImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL), dstImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_ACCESS_TRANSFER_WRITE_BIT); VK_ACCESS_TRANSFER_WRITE_BIT);
@ -2633,17 +2633,10 @@ namespace dxvk {
m_transitions.recordCommands(m_cmd); m_transitions.recordCommands(m_cmd);
VkImageResolve imageRegion;
imageRegion.srcSubresource = srcSubresources;
imageRegion.srcOffset = VkOffset3D { 0, 0, 0 };
imageRegion.dstSubresource = dstSubresources;
imageRegion.dstOffset = VkOffset3D { 0, 0, 0 };
imageRegion.extent = srcImage->mipLevelExtent(srcSubresources.mipLevel);
m_cmd->cmdResolveImage( m_cmd->cmdResolveImage(
srcImage->handle(), srcImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL), srcImage->handle(), srcImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
dstImage->handle(), dstImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL), dstImage->handle(), dstImage->pickLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL),
1, &imageRegion); 1, &region);
m_barriers.accessImage( m_barriers.accessImage(
dstImage, dstSubresourceRange, dstImage, dstSubresourceRange,
@ -2670,12 +2663,11 @@ namespace dxvk {
void DxvkContext::resolveImageFb( void DxvkContext::resolveImageFb(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources, const VkImageResolve& region,
VkFormat format) { VkFormat format) {
auto dstSubresourceRange = vk::makeSubresourceRange(dstSubresources); auto dstSubresourceRange = vk::makeSubresourceRange(region.dstSubresource);
auto srcSubresourceRange = vk::makeSubresourceRange(srcSubresources); auto srcSubresourceRange = vk::makeSubresourceRange(region.srcSubresource);
if (m_barriers.isImageDirty(dstImage, dstSubresourceRange, DxvkAccess::Write) if (m_barriers.isImageDirty(dstImage, dstSubresourceRange, DxvkAccess::Write)
|| m_barriers.isImageDirty(srcImage, srcSubresourceRange, DxvkAccess::Write)) || m_barriers.isImageDirty(srcImage, srcSubresourceRange, DxvkAccess::Write))
@ -2700,31 +2692,32 @@ namespace dxvk {
dstViewInfo.type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; dstViewInfo.type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
dstViewInfo.format = format; dstViewInfo.format = format;
dstViewInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; dstViewInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
dstViewInfo.aspect = dstSubresources.aspectMask; dstViewInfo.aspect = region.dstSubresource.aspectMask;
dstViewInfo.minLevel = dstSubresources.mipLevel; dstViewInfo.minLevel = region.dstSubresource.mipLevel;
dstViewInfo.numLevels = 1; dstViewInfo.numLevels = 1;
dstViewInfo.minLayer = dstSubresources.baseArrayLayer; dstViewInfo.minLayer = region.dstSubresource.baseArrayLayer;
dstViewInfo.numLayers = dstSubresources.layerCount; dstViewInfo.numLayers = region.dstSubresource.layerCount;
DxvkImageViewCreateInfo srcViewInfo; DxvkImageViewCreateInfo srcViewInfo;
srcViewInfo.type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; srcViewInfo.type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
srcViewInfo.format = format; srcViewInfo.format = format;
srcViewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; srcViewInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
srcViewInfo.aspect = srcSubresources.aspectMask; srcViewInfo.aspect = region.srcSubresource.aspectMask;
srcViewInfo.minLevel = srcSubresources.mipLevel; srcViewInfo.minLevel = region.srcSubresource.mipLevel;
srcViewInfo.numLevels = 1; srcViewInfo.numLevels = 1;
srcViewInfo.minLayer = srcSubresources.baseArrayLayer; srcViewInfo.minLayer = region.srcSubresource.baseArrayLayer;
srcViewInfo.numLayers = srcSubresources.layerCount; srcViewInfo.numLayers = region.srcSubresource.layerCount;
Rc<DxvkImageView> dstImageView = m_device->createImageView(dstImage, dstViewInfo); Rc<DxvkImageView> dstImageView = m_device->createImageView(dstImage, dstViewInfo);
Rc<DxvkImageView> srcImageView = m_device->createImageView(srcImage, srcViewInfo); Rc<DxvkImageView> srcImageView = m_device->createImageView(srcImage, srcViewInfo);
// Create a framebuffer and pipeline for the resolve op // Create a framebuffer and pipeline for the resolve op
Rc<DxvkMetaResolveRenderPass> fb = new DxvkMetaResolveRenderPass(
m_device->vkd(), dstImageView, srcImageView);
VkExtent3D passExtent = dstImageView->mipLevelExtent(0); VkExtent3D passExtent = dstImageView->mipLevelExtent(0);
Rc<DxvkMetaResolveRenderPass> fb = new DxvkMetaResolveRenderPass(
m_device->vkd(), dstImageView, srcImageView,
dstImage->isFullSubresource(region.dstSubresource, region.extent));
auto pipeInfo = m_metaResolve->getPipeline( auto pipeInfo = m_metaResolve->getPipeline(
format, srcImage->info().sampleCount); format, srcImage->info().sampleCount);
@ -2748,16 +2741,16 @@ namespace dxvk {
m_cmd->updateDescriptorSets(1, &descriptorWrite); m_cmd->updateDescriptorSets(1, &descriptorWrite);
VkViewport viewport; VkViewport viewport;
viewport.x = 0.0f; viewport.x = float(region.dstOffset.x);
viewport.y = 0.0f; viewport.y = float(region.dstOffset.y);
viewport.width = float(passExtent.width); viewport.width = float(region.extent.width);
viewport.height = float(passExtent.height); viewport.height = float(region.extent.height);
viewport.minDepth = 0.0f; viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f; viewport.maxDepth = 1.0f;
VkRect2D scissor; VkRect2D scissor;
scissor.offset = { 0, 0 }; scissor.offset = { region.dstOffset.x, region.dstOffset.y };
scissor.extent = { passExtent.width, passExtent.height }; scissor.extent = { region.extent.width, region.extent.height };
VkRenderPassBeginInfo info; VkRenderPassBeginInfo info;
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
@ -2770,13 +2763,20 @@ namespace dxvk {
info.pClearValues = nullptr; info.pClearValues = nullptr;
// Perform the actual resolve operation // Perform the actual resolve operation
VkOffset2D srcOffset = {
region.srcOffset.x,
region.srcOffset.y };
m_cmd->cmdBeginRenderPass(&info, VK_SUBPASS_CONTENTS_INLINE); m_cmd->cmdBeginRenderPass(&info, VK_SUBPASS_CONTENTS_INLINE);
m_cmd->cmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeInfo.pipeHandle); m_cmd->cmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeInfo.pipeHandle);
m_cmd->cmdBindDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, m_cmd->cmdBindDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS,
pipeInfo.pipeLayout, descriptorWrite.dstSet, 0, nullptr); pipeInfo.pipeLayout, descriptorWrite.dstSet, 0, nullptr);
m_cmd->cmdSetViewport(0, 1, &viewport); m_cmd->cmdSetViewport(0, 1, &viewport);
m_cmd->cmdSetScissor (0, 1, &scissor); m_cmd->cmdSetScissor (0, 1, &scissor);
m_cmd->cmdDraw(1, dstSubresources.layerCount, 0, 0); m_cmd->cmdPushConstants(pipeInfo.pipeLayout,
VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(srcOffset), &srcOffset);
m_cmd->cmdDraw(1, region.dstSubresource.layerCount, 0, 0);
m_cmd->cmdEndRenderPass(); m_cmd->cmdEndRenderPass();
m_barriers.accessImage( m_barriers.accessImage(

View File

@ -629,16 +629,14 @@ namespace dxvk {
* If it is \c VK_FORMAT_UNDEFINED, the resolve operation * If it is \c VK_FORMAT_UNDEFINED, the resolve operation
* will use the source image format. * will use the source image format.
* \param [in] dstImage Destination image * \param [in] dstImage Destination image
* \param [in] dstSubresources Subresources to write to
* \param [in] srcImage Source image * \param [in] srcImage Source image
* \param [in] srcSubresources Subresources to read from * \param [in] region Region to resolve
* \param [in] format Format for the resolve operation * \param [in] format Format for the resolve operation
*/ */
void resolveImage( void resolveImage(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources, const VkImageResolve& region,
VkFormat format); VkFormat format);
/** /**
@ -950,15 +948,13 @@ namespace dxvk {
void resolveImageHw( void resolveImageHw(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources); const VkImageResolve& region);
void resolveImageFb( void resolveImageFb(
const Rc<DxvkImage>& dstImage, const Rc<DxvkImage>& dstImage,
const VkImageSubresourceLayers& dstSubresources,
const Rc<DxvkImage>& srcImage, const Rc<DxvkImage>& srcImage,
const VkImageSubresourceLayers& srcSubresources, const VkImageResolve& region,
VkFormat format); VkFormat format);
void updatePredicate( void updatePredicate(

View File

@ -13,11 +13,12 @@ namespace dxvk {
DxvkMetaResolveRenderPass::DxvkMetaResolveRenderPass( DxvkMetaResolveRenderPass::DxvkMetaResolveRenderPass(
const Rc<vk::DeviceFn>& vkd, const Rc<vk::DeviceFn>& vkd,
const Rc<DxvkImageView>& dstImageView, const Rc<DxvkImageView>& dstImageView,
const Rc<DxvkImageView>& srcImageView) const Rc<DxvkImageView>& srcImageView,
bool discardDst)
: m_vkd(vkd), : m_vkd(vkd),
m_dstImageView(dstImageView), m_dstImageView(dstImageView),
m_srcImageView(srcImageView), m_srcImageView(srcImageView),
m_renderPass (createRenderPass ()), m_renderPass (createRenderPass(discardDst)),
m_framebuffer (createFramebuffer()) { } m_framebuffer (createFramebuffer()) { }
@ -27,17 +28,22 @@ namespace dxvk {
} }
VkRenderPass DxvkMetaResolveRenderPass::createRenderPass() const { VkRenderPass DxvkMetaResolveRenderPass::createRenderPass(bool discard) const {
VkAttachmentDescription attachment; VkAttachmentDescription attachment;
attachment.flags = 0; attachment.flags = 0;
attachment.format = m_dstImageView->info().format; attachment.format = m_dstImageView->info().format;
attachment.samples = VK_SAMPLE_COUNT_1_BIT; attachment.samples = VK_SAMPLE_COUNT_1_BIT;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachment.initialLayout = m_dstImageView->imageInfo().layout;
attachment.finalLayout = m_dstImageView->imageInfo().layout; attachment.finalLayout = m_dstImageView->imageInfo().layout;
if (discard) {
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
}
VkAttachmentReference dstRef; VkAttachmentReference dstRef;
dstRef.attachment = 0; dstRef.attachment = 0;
@ -274,14 +280,19 @@ namespace dxvk {
VkPipelineLayout DxvkMetaResolveObjects::createPipelineLayout( VkPipelineLayout DxvkMetaResolveObjects::createPipelineLayout(
VkDescriptorSetLayout descriptorSetLayout) const { VkDescriptorSetLayout descriptorSetLayout) const {
VkPushConstantRange push;
push.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
push.offset = 0;
push.size = sizeof(VkOffset2D);
VkPipelineLayoutCreateInfo info; VkPipelineLayoutCreateInfo info;
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
info.pNext = nullptr; info.pNext = nullptr;
info.flags = 0; info.flags = 0;
info.setLayoutCount = 1; info.setLayoutCount = 1;
info.pSetLayouts = &descriptorSetLayout; info.pSetLayouts = &descriptorSetLayout;
info.pushConstantRangeCount = 0; info.pushConstantRangeCount = 1;
info.pPushConstantRanges = nullptr; info.pPushConstantRanges = &push;
VkPipelineLayout result = VK_NULL_HANDLE; VkPipelineLayout result = VK_NULL_HANDLE;
if (m_vkd->vkCreatePipelineLayout(m_vkd->device(), &info, nullptr, &result) != VK_SUCCESS) if (m_vkd->vkCreatePipelineLayout(m_vkd->device(), &info, nullptr, &result) != VK_SUCCESS)

View File

@ -58,7 +58,8 @@ namespace dxvk {
DxvkMetaResolveRenderPass( DxvkMetaResolveRenderPass(
const Rc<vk::DeviceFn>& vkd, const Rc<vk::DeviceFn>& vkd,
const Rc<DxvkImageView>& dstImageView, const Rc<DxvkImageView>& dstImageView,
const Rc<DxvkImageView>& srcImageView); const Rc<DxvkImageView>& srcImageView,
bool discardDst);
~DxvkMetaResolveRenderPass(); ~DxvkMetaResolveRenderPass();
@ -80,7 +81,7 @@ namespace dxvk {
VkRenderPass m_renderPass = VK_NULL_HANDLE; VkRenderPass m_renderPass = VK_NULL_HANDLE;
VkFramebuffer m_framebuffer = VK_NULL_HANDLE; VkFramebuffer m_framebuffer = VK_NULL_HANDLE;
VkRenderPass createRenderPass() const; VkRenderPass createRenderPass(bool discard) const;
VkFramebuffer createFramebuffer() const; VkFramebuffer createFramebuffer() const;

View File

@ -6,8 +6,13 @@ layout(binding = 0) uniform sampler2DMSArray s_image;
layout(location = 0) out vec4 o_color; layout(location = 0) out vec4 o_color;
layout(push_constant)
uniform u_info_t {
ivec2 offset;
} u_info;
void main() { void main() {
ivec3 coord = ivec3(gl_FragCoord.xy, gl_Layer); ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
vec4 color = vec4(0.0f); vec4 color = vec4(0.0f);
for (int i = 0; i < c_samples; i++) for (int i = 0; i < c_samples; i++)
color += texelFetch(s_image, coord, i); color += texelFetch(s_image, coord, i);

View File

@ -9,8 +9,13 @@ uniform sampler2DMSArray s_image;
layout(location = 0) out vec4 o_color; layout(location = 0) out vec4 o_color;
layout(push_constant)
uniform u_info_t {
ivec2 offset;
} u_info;
void main() { void main() {
ivec3 coord = ivec3(gl_FragCoord.xy, gl_Layer); ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
// get a four-bit fragment index for each sample // get a four-bit fragment index for each sample
uint fragMask = fragmentMaskFetchAMD(s_image, coord); uint fragMask = fragmentMaskFetchAMD(s_image, coord);

View File

@ -4,7 +4,12 @@ layout(binding = 0) uniform isampler2DMSArray s_image;
layout(location = 0) out ivec4 o_color; layout(location = 0) out ivec4 o_color;
layout(push_constant)
uniform u_info_t {
ivec2 offset;
} u_info;
void main() { void main() {
ivec3 coord = ivec3(gl_FragCoord.xy, gl_Layer); ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
o_color = texelFetch(s_image, coord, 0); o_color = texelFetch(s_image, coord, 0);
} }

View File

@ -4,7 +4,12 @@ layout(binding = 0) uniform usampler2DMSArray s_image;
layout(location = 0) out uvec4 o_color; layout(location = 0) out uvec4 o_color;
layout(push_constant)
uniform u_info_t {
ivec2 offset;
} u_info;
void main() { void main() {
ivec3 coord = ivec3(gl_FragCoord.xy, gl_Layer); ivec3 coord = ivec3(gl_FragCoord.xy + u_info.offset, gl_Layer);
o_color = texelFetch(s_image, coord, 0); o_color = texelFetch(s_image, coord, 0);
} }