Commit dcc56215 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Implement GL_EXT_multisampled_render_to_texture

This change allows the use of resolve attachments in the Vulkan backend. GL_EXT_multisampled_render_to_texture is implemented using this feature. The infrastructure for specifying resolve attachments is designed with eventual support for GL_EXT_multisampled_render_to_texture2 in mind as well as optimizations to glBlitFramebuffer() and multisampled backbuffers. Proper support for glRenderbufferStorageMultisampledEXT is still missing from this change. All tests use this for the depth/stencil attachment and don't read back the data. Currently, the depth/stencil attachment is created as a normal multisampled image. Bug: angleproject:4836 Change-Id: I110a7f63312ae61a657b6094adf7d97c92bd5843 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2304170 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com>
parent d56ed7cf
......@@ -499,6 +499,9 @@ std::size_t BitSetT<N, BitsT, ParamT>::Iterator::getNextBit()
}
template <size_t N>
using BitSet8 = BitSetT<N, uint8_t>;
template <size_t N>
using BitSet32 = BitSetT<N, uint32_t>;
template <size_t N>
......
......@@ -2415,6 +2415,9 @@ void ContextVk::optimizeRenderPassForPresent(VkFramebuffer framebufferHandle)
// Use finalLayout instead of extra barrier for layout change to present
vk::ImageHelper &image = color0RenderTarget->getImageForWrite();
image.setCurrentImageLayout(vk::ImageLayout::Present);
// TODO(syoussefi): We currently don't store the layout of the resolve attachments, so once
// multisampled backbuffers are optimized to use resolve attachments, this information needs to
// be stored somewhere. http://anglebug.com/4836
mRenderPassCommands->updateRenderPassAttachmentFinalLayout(0, image.getCurrentImageLayout());
}
......
......@@ -1201,6 +1201,46 @@ angle::Result FramebufferVk::resolveColorWithCommand(ContextVk *contextVk,
return angle::Result::Continue;
}
angle::Result FramebufferVk::copyResolveToMultisampedAttachment(ContextVk *contextVk,
RenderTargetVk *colorRenderTarget)
{
ASSERT(colorRenderTarget->hasResolveAttachment());
ASSERT(colorRenderTarget->isImageTransient());
vk::ImageHelper *src = &colorRenderTarget->getResolveImageForRenderPass();
vk::ImageHelper *dest = &colorRenderTarget->getImageForRenderPass();
const vk::ImageView *srcView;
const vk::ImageView *destView;
ANGLE_TRY(colorRenderTarget->getAndRetainCopyImageView(contextVk, &srcView));
ANGLE_TRY(colorRenderTarget->getImageView(contextVk, &destView));
// Note: neither vkCmdCopyImage nor vkCmdBlitImage allow the destination to be multisampled.
// There's no choice but to use a draw-based path to perform this copy.
gl::Extents extents = colorRenderTarget->getExtents();
uint32_t levelVK = colorRenderTarget->getLevelIndex() - src->getBaseLevel();
uint32_t layer = colorRenderTarget->getLayerIndex();
UtilsVk::CopyImageParameters params;
params.srcOffset[0] = 0;
params.srcOffset[1] = 0;
params.srcExtents[0] = extents.width;
params.srcExtents[1] = extents.height;
params.destOffset[0] = 0;
params.destOffset[1] = 0;
params.srcMip = levelVK;
params.srcLayer = layer;
params.srcHeight = extents.height;
params.srcPremultiplyAlpha = false;
params.srcUnmultiplyAlpha = false;
params.srcFlipY = false;
params.destFlipY = false;
params.srcRotation = SurfaceRotation::Identity;
return contextVk->getUtils().copyImage(contextVk, dest, destView, src, srcView, params);
}
bool FramebufferVk::checkStatus(const gl::Context *context) const
{
// if we have both a depth and stencil buffer, they must refer to the same object
......@@ -1392,7 +1432,10 @@ angle::Result FramebufferVk::updateColorAttachment(const gl::Context *context,
updateActiveColorMasks(colorIndexGL, false, false, false, false);
}
if (renderTarget && mState.getEnabledDrawBuffers()[colorIndexGL])
const bool enabledColor = renderTarget && mState.getEnabledDrawBuffers()[colorIndexGL];
const bool enabledResolve = enabledColor && renderTarget->hasResolveAttachment();
if (enabledColor)
{
mCurrentFramebufferDesc.updateColor(colorIndexGL,
renderTarget->getAssignImageViewSerial(contextVk));
......@@ -1402,6 +1445,16 @@ angle::Result FramebufferVk::updateColorAttachment(const gl::Context *context,
mCurrentFramebufferDesc.updateColor(colorIndexGL, kInvalidImageViewSerial);
}
if (enabledResolve)
{
mCurrentFramebufferDesc.updateColorResolve(
colorIndexGL, renderTarget->getAssignResolveImageViewSerial(contextVk));
}
else
{
mCurrentFramebufferDesc.updateColorResolve(colorIndexGL, kInvalidImageViewSerial);
}
return angle::Result::Continue;
}
......@@ -1492,6 +1545,12 @@ angle::Result FramebufferVk::syncState(const gl::Context *context,
mCurrentFramebufferDesc.updateColor(
static_cast<uint32_t>(colorIndexGL),
renderTarget->getAssignImageViewSerial(contextVk));
if (renderTarget->hasResolveAttachment())
{
mCurrentFramebufferDesc.updateColorResolve(
static_cast<uint32_t>(colorIndexGL),
renderTarget->getAssignResolveImageViewSerial(contextVk));
}
}
updateDepthStencilAttachmentSerial(contextVk);
break;
......@@ -1580,6 +1639,7 @@ void FramebufferVk::updateRenderPassDesc()
mRenderPassDesc = {};
mRenderPassDesc.setSamples(getSamples());
// Color attachments.
const auto &colorRenderTargets = mRenderTargetCache.getColors();
const gl::DrawBufferMask enabledDrawBuffers = mState.getEnabledDrawBuffers();
for (size_t colorIndexGL = 0; colorIndexGL < enabledDrawBuffers.size(); ++colorIndexGL)
......@@ -1591,6 +1651,12 @@ void FramebufferVk::updateRenderPassDesc()
mRenderPassDesc.packColorAttachment(
colorIndexGL,
colorRenderTarget->getImageForRenderPass().getFormat().intendedFormatID);
// Add the resolve attachment, if any.
if (colorRenderTarget->hasResolveAttachment())
{
mRenderPassDesc.packColorResolveAttachment(colorIndexGL);
}
}
else
{
......@@ -1598,6 +1664,7 @@ void FramebufferVk::updateRenderPassDesc()
}
}
// Depth/stencil attachment.
RenderTargetVk *depthStencilRenderTarget = getDepthStencilRenderTarget();
if (depthStencilRenderTarget)
{
......@@ -1642,6 +1709,7 @@ angle::Result FramebufferVk::getFramebuffer(ContextVk *contextVk, vk::Framebuffe
std::vector<VkImageView> attachments;
gl::Extents attachmentsSize;
// Color attachments.
const auto &colorRenderTargets = mRenderTargetCache.getColors();
for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
{
......@@ -1657,6 +1725,7 @@ angle::Result FramebufferVk::getFramebuffer(ContextVk *contextVk, vk::Framebuffe
attachmentsSize = colorRenderTarget->getExtents();
}
// Depth/stencil attachment.
RenderTargetVk *depthStencilRenderTarget = getDepthStencilRenderTarget();
if (depthStencilRenderTarget)
{
......@@ -1670,6 +1739,23 @@ angle::Result FramebufferVk::getFramebuffer(ContextVk *contextVk, vk::Framebuffe
attachmentsSize = depthStencilRenderTarget->getExtents();
}
// Color resolve attachments.
for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
{
RenderTargetVk *colorRenderTarget = colorRenderTargets[colorIndexGL];
ASSERT(colorRenderTarget);
if (colorRenderTarget->hasResolveAttachment())
{
const vk::ImageView *resolveImageView = nullptr;
ANGLE_TRY(colorRenderTarget->getResolveImageView(contextVk, &resolveImageView));
attachments.push_back(resolveImageView->getHandle());
ASSERT(!attachmentsSize.empty());
}
}
if (attachmentsSize.empty())
{
// No attachments, so use the default values.
......@@ -1932,12 +2018,13 @@ angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
vk::Framebuffer *framebuffer = nullptr;
ANGLE_TRY(getFramebuffer(contextVk, &framebuffer));
vk::AttachmentOpsArray renderPassAttachmentOps;
vk::ClearValuesArray packedClearValues;
ANGLE_TRY(contextVk->endRenderPass());
// Initialize RenderPass info.
vk::AttachmentOpsArray renderPassAttachmentOps;
vk::ClearValuesArray packedClearValues;
// Color attachments.
const auto &colorRenderTargets = mRenderTargetCache.getColors();
uint32_t currentAttachmentCount = 0;
for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
......@@ -1948,10 +2035,14 @@ angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
renderPassAttachmentOps.setLayouts(currentAttachmentCount, vk::ImageLayout::ColorAttachment,
vk::ImageLayout::ColorAttachment);
const VkAttachmentStoreOp storeOp = colorRenderTarget->isImageTransient()
? VK_ATTACHMENT_STORE_OP_DONT_CARE
: VK_ATTACHMENT_STORE_OP_STORE;
if (mDeferredClears.test(colorIndexGL))
{
renderPassAttachmentOps.setOps(currentAttachmentCount, VK_ATTACHMENT_LOAD_OP_CLEAR,
VK_ATTACHMENT_STORE_OP_STORE);
storeOp);
packedClearValues.store(currentAttachmentCount, VK_IMAGE_ASPECT_COLOR_BIT,
mDeferredClears[colorIndexGL]);
mDeferredClears.reset(colorIndexGL);
......@@ -1962,7 +2053,7 @@ angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
colorRenderTarget->hasDefinedContent()
? VK_ATTACHMENT_LOAD_OP_LOAD
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_STORE);
storeOp);
packedClearValues.store(currentAttachmentCount, VK_IMAGE_ASPECT_COLOR_BIT,
kUninitializedClearValue);
}
......@@ -1970,11 +2061,37 @@ angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
VK_ATTACHMENT_LOAD_OP_DONT_CARE,
VK_ATTACHMENT_STORE_OP_DONT_CARE);
ANGLE_TRY(colorRenderTarget->onColorDraw(contextVk));
// If there's a resolve attachment, and loadOp needs to be LOAD, the multisampled attachment
// needs to take its value from the resolve attachment. In this case, there's no choice
// but to blit from the resolve image into the multisampled one. It's expected that
// application code results in a clear of the framebuffer at the start of the renderpass, so
// the multisampled image is truely transient.
//
// Note that this only needs to be done if the multisampled image and the resolve attachment
// come from the same source. When optimizing glBlitFramebuffer for example, this is not
// the case. isImageTransient() indicates whether this should happen.
//
// TODO: In an ideal world, this could be done in a subpass so that the multisampled data
// always ever stays on a tiled renderer's tile and no memory backing is allocated for it.
// http://anglebug.com/4881
if (colorRenderTarget->hasResolveAttachment() && colorRenderTarget->isImageTransient() &&
renderPassAttachmentOps[currentAttachmentCount].loadOp == VK_ATTACHMENT_LOAD_OP_LOAD)
{
ANGLE_TRY(copyResolveToMultisampedAttachment(contextVk, colorRenderTarget));
}
currentAttachmentCount++;
}
// Transition the images to the correct layout (through onColorDraw) after the
// resolve-to-multisampled copies are done.
for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
{
RenderTargetVk *colorRenderTarget = colorRenderTargets[colorIndexGL];
ANGLE_TRY(colorRenderTarget->onColorDraw(contextVk));
}
// Depth/stencil attachment.
RenderTargetVk *depthStencilRenderTarget = getDepthStencilRenderTarget();
if (depthStencilRenderTarget)
{
......@@ -1989,6 +2106,12 @@ angle::Result FramebufferVk::startNewRenderPass(ContextVk *contextVk,
stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
}
if (depthStencilRenderTarget->isImageTransient())
{
depthStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
}
renderPassAttachmentOps.setLayouts(currentAttachmentCount,
vk::ImageLayout::DepthStencilAttachment,
vk::ImageLayout::DepthStencilAttachment);
......
......@@ -152,6 +152,11 @@ class FramebufferVk : public FramebufferImpl
const UtilsVk::BlitResolveParameters &params,
vk::ImageHelper *srcImage);
// If resolve attachments are used, some use cases require that the multisampled image (whose
// data is normally discarded) take its data from the resolve attachment.
angle::Result copyResolveToMultisampedAttachment(ContextVk *contextVk,
RenderTargetVk *colorRenderTarget);
angle::Result getFramebuffer(ContextVk *contextVk, vk::Framebuffer **framebufferOut);
angle::Result clearImpl(const gl::Context *context,
......
......@@ -28,6 +28,8 @@ RenderTargetVk::~RenderTargetVk() {}
RenderTargetVk::RenderTargetVk(RenderTargetVk &&other)
: mImage(other.mImage),
mImageViews(other.mImageViews),
mResolveImage(other.mResolveImage),
mResolveImageViews(other.mResolveImageViews),
mLevelIndexGL(other.mLevelIndexGL),
mLayerIndex(other.mLayerIndex),
mContentDefined(other.mContentDefined)
......@@ -37,48 +39,74 @@ RenderTargetVk::RenderTargetVk(RenderTargetVk &&other)
void RenderTargetVk::init(vk::ImageHelper *image,
vk::ImageViewHelper *imageViews,
vk::ImageHelper *resolveImage,
vk::ImageViewHelper *resolveImageViews,
uint32_t levelIndexGL,
uint32_t layerIndex)
uint32_t layerIndex,
bool isImageTransient)
{
mImage = image;
mImageViews = imageViews;
mResolveImage = resolveImage;
mResolveImageViews = resolveImageViews;
mLevelIndexGL = levelIndexGL;
mLayerIndex = layerIndex;
// Conservatively assume the content is defined.
mContentDefined = true;
mIsImageTransient = isImageTransient;
}
void RenderTargetVk::reset()
{
mImage = nullptr;
mImageViews = nullptr;
mResolveImage = nullptr;
mResolveImageViews = nullptr;
mLevelIndexGL = 0;
mLayerIndex = 0;
mContentDefined = false;
}
ImageViewSerial RenderTargetVk::getAssignImageViewSerial(ContextVk *contextVk) const
ImageViewSerial RenderTargetVk::getAssignViewSerialImpl(ContextVk *contextVk,
vk::ImageViewHelper *imageViews) const
{
ASSERT(mImageViews);
ASSERT(imageViews);
ASSERT(mLayerIndex < std::numeric_limits<uint16_t>::max());
ASSERT(mLevelIndexGL < std::numeric_limits<uint16_t>::max());
ImageViewSerial imageViewSerial =
mImageViews->getAssignSerial(contextVk, mLevelIndexGL, mLayerIndex);
imageViews->getAssignSerial(contextVk, mLevelIndexGL, mLayerIndex);
ASSERT(imageViewSerial.getValue() < std::numeric_limits<uint32_t>::max());
return imageViewSerial;
}
ImageViewSerial RenderTargetVk::getAssignImageViewSerial(ContextVk *contextVk) const
{
return getAssignViewSerialImpl(contextVk, mImageViews);
}
ImageViewSerial RenderTargetVk::getAssignResolveImageViewSerial(ContextVk *contextVk) const
{
return getAssignViewSerialImpl(contextVk, mResolveImageViews);
}
angle::Result RenderTargetVk::onColorDraw(ContextVk *contextVk)
{
ASSERT(!mImage->getFormat().actualImageFormat().hasDepthOrStencilBits());
contextVk->onRenderPassImageWrite(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::ColorAttachment,
mImage);
mContentDefined = true;
if (mResolveImage)
{
contextVk->onRenderPassImageWrite(VK_IMAGE_ASPECT_COLOR_BIT,
vk::ImageLayout::ColorAttachment, mResolveImage);
}
retainImageViews(contextVk);
mContentDefined = true;
return angle::Result::Continue;
}
......@@ -90,9 +118,15 @@ angle::Result RenderTargetVk::onDepthStencilDraw(ContextVk *contextVk)
VkImageAspectFlags aspectFlags = vk::GetDepthStencilAspectFlags(format);
contextVk->onRenderPassImageWrite(aspectFlags, vk::ImageLayout::DepthStencilAttachment, mImage);
mContentDefined = true;
if (mResolveImage)
{
contextVk->onRenderPassImageWrite(aspectFlags, vk::ImageLayout::DepthStencilAttachment,
mResolveImage);
}
retainImageViews(contextVk);
mContentDefined = true;
return angle::Result::Continue;
}
......@@ -108,21 +142,59 @@ const vk::ImageHelper &RenderTargetVk::getImageForRenderPass() const
return *mImage;
}
angle::Result RenderTargetVk::getImageView(ContextVk *contextVk,
vk::ImageHelper &RenderTargetVk::getResolveImageForRenderPass()
{
ASSERT(mResolveImage && mResolveImage->valid());
return *mResolveImage;
}
const vk::ImageHelper &RenderTargetVk::getResolveImageForRenderPass() const
{
ASSERT(mResolveImage && mResolveImage->valid());
return *mResolveImage;
}
angle::Result RenderTargetVk::getImageViewImpl(ContextVk *contextVk,
const vk::ImageHelper &image,
vk::ImageViewHelper *imageViews,
const vk::ImageView **imageViewOut) const
{
ASSERT(mImage && mImage->valid() && mImageViews);
ASSERT(image.valid() && imageViews);
int32_t levelVK = mLevelIndexGL - mImage->getBaseLevel();
return mImageViews->getLevelLayerDrawImageView(contextVk, *mImage, levelVK, mLayerIndex,
return imageViews->getLevelLayerDrawImageView(contextVk, image, levelVK, mLayerIndex,
imageViewOut);
}
angle::Result RenderTargetVk::getImageView(ContextVk *contextVk,
const vk::ImageView **imageViewOut) const
{
ASSERT(mImage);
return getImageViewImpl(contextVk, *mImage, mImageViews, imageViewOut);
}
angle::Result RenderTargetVk::getResolveImageView(ContextVk *contextVk,
const vk::ImageView **imageViewOut) const
{
ASSERT(mResolveImage);
return getImageViewImpl(contextVk, *mResolveImage, mResolveImageViews, imageViewOut);
}
bool RenderTargetVk::isResolveImageOwnerOfData() const
{
// If there's a resolve attachment and the image itself is transient, it's the resolve
// attachment that owns the data, so all non-render-pass accesses to the render target data
// should go through the resolve attachment.
return hasResolveAttachment() && isImageTransient();
}
angle::Result RenderTargetVk::getAndRetainCopyImageView(ContextVk *contextVk,
const vk::ImageView **imageViewOut) const
{
retainImageViews(contextVk);
const vk::ImageViewHelper *imageViews = mImageViews;
const vk::ImageViewHelper *imageViews =
isResolveImageOwnerOfData() ? mResolveImageViews : mImageViews;
// If the source of render target is a texture or renderbuffer, this will always be valid. This
// is also where 3D or 2DArray images could be the source of the render target.
if (imageViews->hasCopyImageView())
......@@ -132,8 +204,10 @@ angle::Result RenderTargetVk::getAndRetainCopyImageView(ContextVk *contextVk,
}
// Otherwise, this must come from the surface, in which case the image is 2D, so the image view
// used to draw is just as good for fetching.
return getImageView(contextVk, imageViewOut);
// used to draw is just as good for fetching. If resolve attachment is present, fetching is
// done from that.
return isResolveImageOwnerOfData() ? getResolveImageView(contextVk, imageViewOut)
: getImageView(contextVk, imageViewOut);
}
const vk::Format &RenderTargetVk::getImageFormat() const
......@@ -149,23 +223,28 @@ gl::Extents RenderTargetVk::getExtents() const
return mImage->getLevelExtents2D(levelVK);
}
void RenderTargetVk::updateSwapchainImage(vk::ImageHelper *image, vk::ImageViewHelper *imageViews)
void RenderTargetVk::updateSwapchainImage(vk::ImageHelper *image,
vk::ImageViewHelper *imageViews,
vk::ImageHelper *resolveImage,
vk::ImageViewHelper *resolveImageViews)
{
ASSERT(image && image->valid() && imageViews);
mImage = image;
mImageViews = imageViews;
mResolveImage = resolveImage;
mResolveImageViews = resolveImageViews;
}
vk::ImageHelper &RenderTargetVk::getImageForCopy() const
{
ASSERT(mImage && mImage->valid());
return *mImage;
ASSERT(mImage && mImage->valid() && (mResolveImage == nullptr || mResolveImage->valid()));
return isResolveImageOwnerOfData() ? *mResolveImage : *mImage;
}
vk::ImageHelper &RenderTargetVk::getImageForWrite() const
{
ASSERT(mImage && mImage->valid());
return *mImage;
ASSERT(mImage && mImage->valid() && (mResolveImage == nullptr || mResolveImage->valid()));
return isResolveImageOwnerOfData() ? *mResolveImage : *mImage;
}
angle::Result RenderTargetVk::flushStagedUpdates(ContextVk *contextVk,
......@@ -176,6 +255,8 @@ angle::Result RenderTargetVk::flushStagedUpdates(ContextVk *contextVk,
// contents. Therefore, set mContentDefined so that the next render pass will have loadOp=LOAD.
mContentDefined = true;
ASSERT(mImage->valid() && (!isResolveImageOwnerOfData() || mResolveImage->valid()));
// Note that the layer index for 3D textures is always zero according to Vulkan.
uint32_t layerIndex = mLayerIndex;
if (mImage->getType() == VK_IMAGE_TYPE_3D)
......@@ -183,19 +264,37 @@ angle::Result RenderTargetVk::flushStagedUpdates(ContextVk *contextVk,
layerIndex = 0;
}
ASSERT(mImage->valid());
if (!mImage->isUpdateStaged(mLevelIndexGL, layerIndex))
vk::ImageHelper *image = isResolveImageOwnerOfData() ? mResolveImage : mImage;
// All updates should be staged on the image that owns the data as the source of truth. With
// multisampled-render-to-texture framebuffers, that is the resolve image. In that case, even
// though deferred clears set the loadOp of the transient multisampled image, the clears
// themselves are staged on the resolve image. The |flushSingleSubresourceStagedUpdates| call
// below will either flush all staged updates to the resolve image, or if the only staged update
// is a clear, it will accumulate it in the |deferredClears| array. Later, when the render pass
// is started, the deferred clears are applied to the transient multisampled image.
ASSERT(!isResolveImageOwnerOfData() || !mImage->isUpdateStaged(mLevelIndexGL, layerIndex));
ASSERT(isResolveImageOwnerOfData() || mResolveImage == nullptr ||
!mResolveImage->isUpdateStaged(mLevelIndexGL, layerIndex));
if (!image->isUpdateStaged(mLevelIndexGL, layerIndex))
{
return angle::Result::Continue;
}
vk::CommandBuffer *commandBuffer;
ANGLE_TRY(contextVk->endRenderPassAndGetCommandBuffer(&commandBuffer));
return mImage->flushSingleSubresourceStagedUpdates(
return image->flushSingleSubresourceStagedUpdates(
contextVk, mLevelIndexGL, layerIndex, commandBuffer, deferredClears, deferredClearIndex);
}
void RenderTargetVk::retainImageViews(ContextVk *contextVk) const
{
mImageViews->retain(&contextVk->getResourceUseList());
if (mResolveImageViews)
{
mResolveImageViews->retain(&contextVk->getResourceUseList());
}
}
gl::ImageIndex RenderTargetVk::getImageIndex() const
......
......@@ -44,11 +44,15 @@ class RenderTargetVk final : public FramebufferAttachmentRenderTarget
void init(vk::ImageHelper *image,
vk::ImageViewHelper *imageViews,
vk::ImageHelper *resolveImage,
vk::ImageViewHelper *resolveImageViews,
uint32_t levelIndexGL,
uint32_t layerIndex);
uint32_t layerIndex,
bool isImageTransient);
void reset();
// This returns the serial from underlying ImageViewHelper, first assigning one if required
ImageViewSerial getAssignImageViewSerial(ContextVk *contextVk) const;
ImageViewSerial getAssignResolveImageViewSerial(ContextVk *contextVk) const;
// Note: RenderTargets should be called in order, with the depth/stencil onRender last.
angle::Result onColorDraw(ContextVk *contextVk);
......@@ -57,11 +61,16 @@ class RenderTargetVk final : public FramebufferAttachmentRenderTarget
vk::ImageHelper &getImageForRenderPass();
const vk::ImageHelper &getImageForRenderPass() const;
vk::ImageHelper &getResolveImageForRenderPass();
const vk::ImageHelper &getResolveImageForRenderPass() const;
vk::ImageHelper &getImageForCopy() const;
vk::ImageHelper &getImageForWrite() const;
// For cube maps we use single-level single-layer 2D array views.
angle::Result getImageView(ContextVk *contextVk, const vk::ImageView **imageViewOut) const;
angle::Result getResolveImageView(ContextVk *contextVk,
const vk::ImageView **imageViewOut) const;
// For 3D textures, the 2D view created for render target is invalid to read from. The
// following will return a view to the whole image (for all types, including 3D and 2DArray).
......@@ -77,7 +86,10 @@ class RenderTargetVk final : public FramebufferAttachmentRenderTarget
// Special mutator for Surface RenderTargets. Allows the Framebuffer to keep a single
// RenderTargetVk pointer.
void updateSwapchainImage(vk::ImageHelper *image, vk::ImageViewHelper *imageViews);
void updateSwapchainImage(vk::ImageHelper *image,
vk::ImageViewHelper *imageViews,
vk::ImageHelper *resolveImage,
vk::ImageViewHelper *resolveImageViews);
angle::Result flushStagedUpdates(ContextVk *contextVk,
vk::ClearValuesArray *deferredClears,
......@@ -90,15 +102,77 @@ class RenderTargetVk final : public FramebufferAttachmentRenderTarget
// as loadOp of the render target in the next renderpass.
void invalidateEntireContent() { mContentDefined = false; }
// See the description of mIsImageTransient for details of how the following two can
// interact.
bool hasResolveAttachment() const { return mResolveImage != nullptr; }
bool isImageTransient() const { return mIsImageTransient; }
private:
angle::Result getImageViewImpl(ContextVk *contextVk,
const vk::ImageHelper &image,
vk::ImageViewHelper *imageViews,
const vk::ImageView **imageViewOut) const;
ImageViewSerial getAssignViewSerialImpl(ContextVk *contextVk,
vk::ImageViewHelper *imageViews) const;
bool isResolveImageOwnerOfData() const;
// The color or depth/stencil attachment of the framebuffer and its view.
vk::ImageHelper *mImage;
vk::ImageViewHelper *mImageViews;
// If present, this is the corresponding resolve attachment and its view. This is used to
// implement GL_EXT_multisampled_render_to_texture, so while the rendering is done on mImage
// during the renderpass, the resolved image is the one that actually holds the data. This
// means that data uploads and blit are done on this image, copies are done out of this image
// etc. This means that if there is no clear, and hasDefinedContent(), the contents of
// mResolveImage must be copied to mImage since the loadOp of the attachment must be set to
// LOAD.
vk::ImageHelper *mResolveImage;
vk::ImageViewHelper *mResolveImageViews;
// Which subresource of the image is used as render target.
uint32_t mLevelIndexGL;
uint32_t mLayerIndex;
// Whether the render target has been invalidated. If so, DONT_CARE is used instead of LOAD for
// loadOp of this attachment.
bool mContentDefined;
// If resolve attachment exists, |mIsImageTransient| is true if the multisampled results need to
// be discarded.
//
// - GL_EXT_multisampled_render_to_texture: this is true for render targets created for this
// extension's usage. Only color attachments use this optimization at the moment.
// - GL_EXT_multisampled_render_to_texture2: this is true for depth/stencil textures per this
// extension, even though a resolve attachment is not even provided.
// - Multisampled swapchain: TODO(syoussefi) this is true for the multisampled color attachment.
// http://anglebug.com/4836
// - glBlitFramebuffer optimization: TODO(timvp) this is **false** in this case, as the
// multisampled attachment and the resolve attachments belong to independent framebuffers.
// http://anglebug.com/4753
//
// Based on the above, we have:
//
// mResolveImage == nullptr | mResolveImage != nullptr
// |
// Normal rendering | Blit optimization
// !IsTransient No resolve | Resolve
// storeOp = STORE | storeOp = STORE
// Owner of data: mImage | Owner of data: mImage
// |
// ---------------------------------------------+---------------------------------------
// |
// EXT_multisampled_render_to_texture2 | GL_EXT_multisampled_render_to_texture
// | or multisampled Swapchain optimization
// IsTransient No resolve | Resolve
// storeOp = DONT_CARE | storeOp = DONT_CARE
// Owner of data: None (not stored) | Owner of data: mResolveImage
//
// In the above, storeOp of the resolve attachment is always STORE. if !IsTransient, storeOp is
// affected by a framebuffer invalidate call.
bool mIsImageTransient;
};
// A vector of rendertargets
......
......@@ -63,6 +63,11 @@ angle::Result RenderbufferVk::setStorageImpl(const gl::Context *context,
}
}
// TODO(syoussefi): if glRenderbufferStorageMultisampleEXT, need to create the image as
// single-sampled and have a multisampled image for intermediate results. Currently, tests
// seem to only use this for depth/stencil buffers and don't attempt to read from it. This
// needs to be fixed and tests added. http://anglebug.com/4836
if ((mImage == nullptr || !mImage->valid()) && (width != 0 && height != 0))
{
if (mImage == nullptr)
......@@ -87,7 +92,7 @@ angle::Result RenderbufferVk::setStorageImpl(const gl::Context *context,
VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
ANGLE_TRY(mImage->initMemory(contextVk, renderer->getMemoryProperties(), flags));
mRenderTarget.init(mImage, &mImageViews, 0, 0);
mRenderTarget.init(mImage, &mImageViews, nullptr, nullptr, 0, 0, false);
}
return angle::Result::Continue;
......@@ -146,7 +151,8 @@ angle::Result RenderbufferVk::setStorageEGLImageTarget(const gl::Context *contex
imageVk->getImage()->getSamples());
}
mRenderTarget.init(mImage, &mImageViews, imageVk->getImageLevel(), imageVk->getImageLayer());
mRenderTarget.init(mImage, &mImageViews, nullptr, nullptr, imageVk->getImageLevel(),
imageVk->getImageLayer(), false);
return angle::Result::Continue;
}
......
......@@ -126,6 +126,8 @@ angle::Result SurfaceVk::getAttachmentRenderTarget(const gl::Context *context,
GLsizei samples,
FramebufferAttachmentRenderTarget **rtOut)
{
ASSERT(samples == 0);
if (binding == GL_BACK)
{
*rtOut = &mColorRenderTarget;
......@@ -233,9 +235,11 @@ OffscreenSurfaceVk::OffscreenSurfaceVk(const egl::SurfaceState &surfaceState)
mColorAttachment(this),
mDepthStencilAttachment(this)
{
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, 0, 0);
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, nullptr, nullptr,
0, 0, false);
mDepthStencilRenderTarget.init(&mDepthStencilAttachment.image,
&mDepthStencilAttachment.imageViews, 0, 0);
&mDepthStencilAttachment.imageViews, nullptr, nullptr, 0, 0,
false);
}
OffscreenSurfaceVk::~OffscreenSurfaceVk() {}
......@@ -261,7 +265,8 @@ angle::Result OffscreenSurfaceVk::initializeImpl(DisplayVk *displayVk)
{
ANGLE_TRY(mColorAttachment.initialize(
displayVk, mWidth, mHeight, renderer->getFormat(config->renderTargetFormat), samples));
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, 0, 0);
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, nullptr,
nullptr, 0, 0, false);
}
if (config->depthStencilFormat != GL_NONE)
......@@ -269,7 +274,8 @@ angle::Result OffscreenSurfaceVk::initializeImpl(DisplayVk *displayVk)
ANGLE_TRY(mDepthStencilAttachment.initialize(
displayVk, mWidth, mHeight, renderer->getFormat(config->depthStencilFormat), samples));
mDepthStencilRenderTarget.init(&mDepthStencilAttachment.image,
&mDepthStencilAttachment.imageViews, 0, 0);
&mDepthStencilAttachment.imageViews, nullptr, nullptr, 0, 0,
false);
}
return angle::Result::Continue;
......@@ -471,8 +477,9 @@ WindowSurfaceVk::WindowSurfaceVk(const egl::SurfaceState &surfaceState, EGLNativ
{
// Initialize the color render target with the multisampled targets. If not multisampled, the
// render target will be updated to refer to a swapchain image on every acquire.
mColorRenderTarget.init(&mColorImageMS, &mColorImageMSViews, 0, 0);
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageViews, 0, 0);
mColorRenderTarget.init(&mColorImageMS, &mColorImageMSViews, nullptr, nullptr, 0, 0, false);
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageViews, nullptr, nullptr,
0, 0, false);
mDepthStencilImageBinding.bind(&mDepthStencilImage);
mColorImageMSBinding.bind(&mColorImageMS);
}
......@@ -930,7 +937,7 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
// Initialize the color render target with the multisampled targets. If not multisampled,
// the render target will be updated to refer to a swapchain image on every acquire.
mColorRenderTarget.init(&mColorImageMS, &mColorImageMSViews, 0, 0);
mColorRenderTarget.init(&mColorImageMS, &mColorImageMSViews, nullptr, nullptr, 0, 0, false);
}
ANGLE_TRY(resizeSwapchainImages(context, imageCount));
......@@ -953,7 +960,8 @@ angle::Result WindowSurfaceVk::createSwapChain(vk::Context *context,
ANGLE_TRY(mDepthStencilImage.initMemory(context, renderer->getMemoryProperties(),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageViews, 0, 0);
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageViews, nullptr,
nullptr, 0, 0, false);
// We will need to pass depth/stencil image views to the RenderTargetVk in the future.
}
......@@ -1359,7 +1367,7 @@ VkResult WindowSurfaceVk::nextSwapchainImage(vk::Context *context)
// multisampling, as the swapchain image is essentially unused until then.
if (!mColorImageMS.valid())
{
mColorRenderTarget.updateSwapchainImage(&image.image, &image.imageViews);
mColorRenderTarget.updateSwapchainImage(&image.image, &image.imageViews, nullptr, nullptr);
}
// Notify the owning framebuffer there may be staged updates.
......
......@@ -1828,6 +1828,54 @@ angle::Result TextureVk::getAttachmentRenderTarget(const gl::Context *context,
levelCount));
}
// If samples > 1 here, we have a singlesampled texture that's being multisampled rendered to.
// In this case, create a multisampled image that is otherwise identical to the single sampled
// image. That multisampled image is used as color or depth/stencil attachment, while the
// original image is used as the resolve attachment.
if (samples > 1 && !mMultisampledImage.valid())
{
ASSERT(mState.getBaseLevelDesc().samples <= 1);
// The image is used as either color or depth/stencil attachment. Additionally, its memory
// is lazily allocated as the contents are discarded at the end of the renderpass and with
// tiling GPUs no actual backing memory is required.
//
// Note that the Vulkan image is created with or without
// VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT based on whether the memory that will be used to
// create the image would have VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT. TRANSIENT is
// provided if there is any memory that supports LAZILY_ALLOCATED. However, based on actual
// image requirements, such a memory may not be suitable for the image. We don't support
// such a case, which will result in the |initMemory| call below failing.
const vk::MemoryProperties &memoryProperties =
contextVk->getRenderer()->getMemoryProperties();
const bool hasLazilyAllocatedMemory = memoryProperties.hasLazilyAllocatedMemory();
const VkImageUsageFlags kMultisampledUsageFlags =
(hasLazilyAllocatedMemory ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0) |
(mImage->getAspectFlags() == VK_IMAGE_ASPECT_COLOR_BIT
? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
: VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
constexpr VkImageCreateFlags kMultisampledCreateFlags = 0;
ANGLE_TRY(mMultisampledImage.initExternal(
contextVk, mState.getType(), mImage->getExtents(), mImage->getFormat(), samples,
kMultisampledUsageFlags, kMultisampledCreateFlags, rx::vk::ImageLayout::Undefined,
nullptr, mImage->getBaseLevel(), mImage->getMaxLevel(), mImage->getLevelCount(),
mImage->getLayerCount()));
const VkMemoryPropertyFlags kMultisampledMemoryFlags =
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
(hasLazilyAllocatedMemory ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0);
ANGLE_TRY(mMultisampledImage.initMemory(
contextVk, contextVk->getRenderer()->getMemoryProperties(), kMultisampledMemoryFlags));
// Remove the emulated format clear from the multisampled image if any. There is one
// already staged on the resolve image if needed.
mMultisampledImage.removeStagedUpdates(contextVk, mMultisampledImage.getBaseLevel(),
mMultisampledImage.getMaxLevel());
}
// Don't flush staged updates here. We'll handle that in FramebufferVk so it can defer clears.
GLuint layerIndex = 0, layerCount = 0;
......@@ -1902,8 +1950,35 @@ angle::Result TextureVk::initRenderTargets(ContextVk *contextVk,
for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex)
{
vk::ImageHelper *drawImage = mImage;
vk::ImageViewHelper *drawImageViews = &mImageViews;
vk::ImageHelper *resolveImage = nullptr;
vk::ImageViewHelper *resolveImageViews = nullptr;
// If multisampled render to texture, use the multisampled image as draw image instead, and
// resolve into the texture's image automatically.
if (mMultisampledImage.valid())
{
drawImage = &mMultisampledImage;
drawImageViews = &mMultisampledImageViews;
resolveImage = mImage;
resolveImageViews = &mImageViews;
// If the texture is depth/stencil, GL_EXT_multisampled_render_to_texture2 explicitly
// indicates that there is no need for the image to be resolved. In that case, don't
// specify the resolve image. Note that the multisampled image's data is discarded
// nevertheless per this spec.
if (mImage->getAspectFlags() != VK_IMAGE_ASPECT_COLOR_BIT)
{
resolveImage = nullptr;
resolveImageViews = nullptr;
}
}
mRenderTargets[levelIndex][layerIndex].init(
mImage, &mImageViews, getNativeImageLevel(levelIndex), getNativeImageLayer(layerIndex));
drawImage, drawImageViews, resolveImage, resolveImageViews,
getNativeImageLevel(levelIndex), getNativeImageLayer(layerIndex),
mMultisampledImage.valid());
}
return angle::Result::Continue;
}
......@@ -2263,8 +2338,13 @@ void TextureVk::releaseImage(ContextVk *contextVk)
mImage = nullptr;
}
}
if (mMultisampledImage.valid())
{
mMultisampledImage.releaseImage(renderer);
}
mImageViews.release(renderer);
mMultisampledImageViews.release(renderer);
for (RenderTargetVector &renderTargetLevels : mRenderTargets)
{
......
......@@ -420,6 +420,11 @@ class TextureVk : public TextureImpl, public angle::ObserverInterface
// reallocated independently of |mImage| on state changes.
vk::ImageViewHelper mImageViews;
// If multisampled rendering to texture, an intermediate multisampled image is created for use
// as renderpass color or depth/stencil attachment.
vk::ImageHelper mMultisampledImage;
vk::ImageViewHelper mMultisampledImageViews;
// |mSampler| contains the relevant Vulkan sampler states representing the OpenGL Texture
// sampling states for the Texture.
vk::BindingPointer<vk::Sampler> mSampler;
......
......@@ -1790,13 +1790,14 @@ angle::Result UtilsVk::copyImage(ContextVk *contextVk,
renderPassDesc.setSamples(dest->getSamples());
renderPassDesc.packColorAttachment(0, dstFormat.intendedFormatID);
// Multisampled copy is not yet supported.
ASSERT(src->getSamples() == 1 && dest->getSamples() == 1);
// Copy from multisampled image is not supported.
ASSERT(src->getSamples() == 1);
vk::GraphicsPipelineDesc pipelineDesc;
pipelineDesc.initDefaults();
pipelineDesc.setCullMode(VK_CULL_MODE_NONE);
pipelineDesc.setRenderPassDesc(renderPassDesc);
pipelineDesc.setRasterizationSamples(dest->getSamples());
gl::Rectangle renderArea;
renderArea.x = params.destOffset[0];
......@@ -1854,6 +1855,9 @@ angle::Result UtilsVk::copyImage(ContextVk *contextVk,
commandBuffer->draw(6, 0);
descriptorPoolBinding.reset();
// Close the render pass for this temporary framebuffer.
ANGLE_TRY(contextVk->endRenderPass());
return angle::Result::Continue;
}
......
......@@ -115,7 +115,8 @@ angle::Result IOSurfaceSurfaceVkMac::initializeImpl(DisplayVk *displayVk)
displayVk, mWidth, mHeight,
renderer->getFormat(kIOSurfaceFormats[mFormatIndex].nativeSizedInternalFormat), samples,
IOSurfaceGetBaseAddressOfPlane(mIOSurface, mPlane)));
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, 0, 0);
mColorRenderTarget.init(&mColorAttachment.image, &mColorAttachment.imageViews, nullptr, nullptr,
0, 0, false);
return angle::Result::Continue;
}
......
......@@ -35,8 +35,11 @@ namespace
// In the FramebufferDesc object:
// - Depth/stencil serial is at index 0
// - Color serials are at indices [1:gl::IMPLEMENTATION_MAX_DRAW_BUFFERS]
// - Resolve attachments are at indices [gl::IMPLEMENTATION_MAX_DRAW_BUFFERS+1,
// gl::IMPLEMENTATION_MAX_DRAW_BUFFERS*2]
constexpr size_t kFramebufferDescDepthStencilIndex = 0;
constexpr size_t kFramebufferDescColorIndexOffset = 1;
constexpr size_t kFramebufferDescResolveIndexOffset = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS + 1;
uint8_t PackGLBlendOp(GLenum blendOp)
{
......@@ -169,6 +172,31 @@ void UnpackAttachmentDesc(VkAttachmentDescription *desc,
ConvertImageLayoutToVkImageLayout(static_cast<ImageLayout>(ops.finalLayout));
}
void UnpackResolveAttachmentDesc(VkAttachmentDescription *desc, const vk::Format &format)
{
// We would only need this flag for duplicated attachments. Apply it conservatively. In
// practice it's unlikely any application would use the same image as multiple resolve
// attachments simultaneously, so this flag can likely be removed without any issue if it incurs
// a performance penalty.
desc->flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT;
desc->format = format.vkImageFormat;
// We don't support depth/stencil resolve attachments currently.
const angle::Format &angleFormat = format.actualImageFormat();
ASSERT(angleFormat.depthBits == 0 && angleFormat.stencilBits == 0);
// Resolve attachments always have a sample count of 1. For simplicity, unlikely cases where
// the resolve framebuffer is immediately invalidated or cleared are ignored. Therefore, loadOp
// and storeOp can be fixed to DONT_CARE and STORE respectively.
desc->samples = VK_SAMPLE_COUNT_1_BIT;
desc->loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
desc->storeOp = VK_ATTACHMENT_STORE_OP_STORE;
desc->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
desc->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
desc->initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
desc->finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
void UnpackStencilState(const vk::PackedStencilOpState &packedState,
uint8_t stencilReference,
VkStencilOpState *stateOut)
......@@ -218,8 +246,12 @@ angle::Result InitializeRenderPassFromDesc(vk::Context *context,
// Unpack the packed and split representation into the format required by Vulkan.
gl::DrawBuffersVector<VkAttachmentReference> colorAttachmentRefs;
VkAttachmentReference depthStencilAttachmentRef = kUnusedAttachment;
gl::AttachmentArray<VkAttachmentDescription> attachmentDescs;
gl::DrawBuffersVector<VkAttachmentReference> colorResolveAttachmentRefs;
// The list of attachments includes all non-resolve and resolve attachments.
FramebufferAttachmentArray<VkAttachmentDescription> attachmentDescs;
// Pack color attachments
uint32_t colorAttachmentCount = 0;
uint32_t attachmentCount = 0;
for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
......@@ -256,6 +288,7 @@ angle::Result InitializeRenderPassFromDesc(vk::Context *context,
++attachmentCount;
}
// Pack depth/stencil attachment, if any
if (desc.hasDepthStencilAttachment())
{
uint32_t depthStencilIndex = static_cast<uint32_t>(desc.depthStencilAttachmentIndex());
......@@ -274,6 +307,32 @@ angle::Result InitializeRenderPassFromDesc(vk::Context *context,
++attachmentCount;
}
// Pack color resolve attachments
const uint32_t nonResolveAttachmentCount = attachmentCount;
for (uint32_t colorIndexGL = 0; colorIndexGL < desc.colorAttachmentRange(); ++colorIndexGL)
{
if (!desc.hasColorResolveAttachment(colorIndexGL))
{
colorResolveAttachmentRefs.push_back(kUnusedAttachment);
continue;
}
ASSERT(desc.isColorAttachmentEnabled(colorIndexGL));
uint32_t colorResolveIndexVk = attachmentCount;
const vk::Format &format = context->getRenderer()->getFormat(desc[colorIndexGL]);
VkAttachmentReference colorRef;
colorRef.attachment = colorResolveIndexVk;
colorRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorResolveAttachmentRefs.push_back(colorRef);
UnpackResolveAttachmentDesc(&attachmentDescs[colorResolveIndexVk], format);
++attachmentCount;
}
VkSubpassDescription subpassDesc = {};
subpassDesc.flags = 0;
......@@ -282,7 +341,8 @@ angle::Result InitializeRenderPassFromDesc(vk::Context *context,
subpassDesc.pInputAttachments = nullptr;
subpassDesc.colorAttachmentCount = static_cast<uint32_t>(colorAttachmentRefs.size());
subpassDesc.pColorAttachments = colorAttachmentRefs.data();
subpassDesc.pResolveAttachments = nullptr;
subpassDesc.pResolveAttachments =
attachmentCount > nonResolveAttachmentCount ? colorResolveAttachmentRefs.data() : nullptr;
subpassDesc.pDepthStencilAttachment =
(depthStencilAttachmentRef.attachment != VK_ATTACHMENT_UNUSED ? &depthStencilAttachmentRef
: nullptr);
......@@ -453,6 +513,13 @@ void RenderPassDesc::packDepthStencilAttachment(angle::FormatID formatID)
mHasDepthStencilAttachment = true;
}
void RenderPassDesc::packColorResolveAttachment(size_t colorIndexGL)
{
ASSERT(isColorAttachmentEnabled(colorIndexGL));
ASSERT(mLogSamples > 0);
mColorResolveAttachmentMask.set(colorIndexGL);
}
RenderPassDesc &RenderPassDesc::operator=(const RenderPassDesc &other)
{
memcpy(this, &other, sizeof(RenderPassDesc));
......@@ -480,7 +547,7 @@ size_t RenderPassDesc::attachmentCount() const
// Note that there are no gaps in depth/stencil attachments. In fact there is a maximum of 1 of
// it.
return colorAttachmentCount + mHasDepthStencilAttachment;
return colorAttachmentCount + mColorResolveAttachmentMask.count() + mHasDepthStencilAttachment;
}
bool operator==(const RenderPassDesc &lhs, const RenderPassDesc &rhs)
......@@ -1748,6 +1815,11 @@ void FramebufferDesc::updateColor(uint32_t index, ImageViewSerial serial)
update(kFramebufferDescColorIndexOffset + index, serial);
}
void FramebufferDesc::updateColorResolve(uint32_t index, ImageViewSerial serial)
{
update(kFramebufferDescResolveIndexOffset + index, serial);
}
void FramebufferDesc::updateDepthStencil(ImageViewSerial serial)
{
update(kFramebufferDescDepthStencilIndex, serial);
......
......@@ -75,6 +75,8 @@ class alignas(4) RenderPassDesc final
// The caller must pack the depth/stencil attachment last, which is packed right after the color
// attachments (including gaps), i.e. with an index starting from |colorAttachmentRange()|.
void packDepthStencilAttachment(angle::FormatID angleFormatID);
// Indicate that a color attachment should have a corresponding resolve attachment.
void packColorResolveAttachment(size_t colorIndexGL);
size_t hash() const;
......@@ -84,6 +86,10 @@ class alignas(4) RenderPassDesc final
bool isColorAttachmentEnabled(size_t colorIndexGL) const;
bool hasDepthStencilAttachment() const { return mHasDepthStencilAttachment; }
bool hasColorResolveAttachment(size_t colorIndexGL) const
{
return mColorResolveAttachmentMask.test(colorIndexGL);
}
// Get the number of attachments in the Vulkan render pass, i.e. after removing disabled
// color attachments.
......@@ -104,8 +110,15 @@ class alignas(4) RenderPassDesc final
uint8_t mLogSamples : 3;
uint8_t mColorAttachmentRange : 4;
uint8_t mHasDepthStencilAttachment : 1;
// Temporary padding for upcoming support for resolve attachments.
ANGLE_MAYBE_UNUSED uint8_t pad;
// Whether each color attachment has a corresponding resolve attachment. Color resolve
// attachments can be used to optimize resolve through glBlitFramebuffer() as well as support
// GL_EXT_multisampled_render_to_texture and GL_EXT_multisampled_render_to_texture2.
//
// Note that depth/stencil resolve attachments require VK_KHR_depth_stencil_resolve which is
// currently not well supported, so ANGLE always takes a fallback path for them. When a resolve
// path is implemented for depth/stencil attachments, another bit must be made free
// (mAttachmentFormats is one element too large, so there are 8 bits there to take).
angle::BitSet8<gl::IMPLEMENTATION_MAX_DRAW_BUFFERS> mColorResolveAttachmentMask;
// Color attachment formats are stored with their GL attachment indices. The depth/stencil
// attachment formats follow the last enabled color attachment. When creating a render pass,
// the disabled attachments are removed and the resulting attachments are packed.
......@@ -126,6 +139,8 @@ class alignas(4) RenderPassDesc final
// - Subpass attachment 2 -> VK_ATTACHMENT_UNUSED
// - Subpass attachment 3 -> Renderpass attachment 1
//
// The resolve attachments are packed after the non-resolve attachments. They use the same
// formats, so they are not specified in this array.
gl::AttachmentArray<uint8_t> mAttachmentFormats;
};
......@@ -837,8 +852,11 @@ class UniformsAndXfbDesc
std::array<BufferSerial, kMaxBufferCount> mBufferSerials;
};
// This is IMPLEMENTATION_MAX_DRAW_BUFFERS + 1 for DS attachment
constexpr size_t kMaxFramebufferAttachments = gl::IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS;
// There can be a maximum of IMPLEMENTATION_MAX_DRAW_BUFFERS color and resolve attachments, plus one
// depth/stencil attachment.
constexpr size_t kMaxFramebufferAttachments = gl::IMPLEMENTATION_MAX_DRAW_BUFFERS * 2 + 1;
template <typename T>
using FramebufferAttachmentArray = std::array<T, kMaxFramebufferAttachments>;
class FramebufferDesc
{
......@@ -850,6 +868,7 @@ class FramebufferDesc
FramebufferDesc &operator=(const FramebufferDesc &other);
void updateColor(uint32_t index, ImageViewSerial serial);
void updateColorResolve(uint32_t index, ImageViewSerial serial);
void updateDepthStencil(ImageViewSerial serial);
size_t hash() const;
void reset();
......@@ -861,7 +880,7 @@ class FramebufferDesc
private:
void update(uint32_t index, ImageViewSerial serial);
gl::AttachmentArray<ImageViewSerial> mSerials;
FramebufferAttachmentArray<ImageViewSerial> mSerials;
uint32_t mMaxValidSerialIndex;
};
......
......@@ -83,6 +83,7 @@ void RendererVk::ensureCapsInitialized() const
mNativeExtensions.fragDepth = true;
mNativeExtensions.framebufferBlit = true;
mNativeExtensions.framebufferMultisample = true;
mNativeExtensions.multisampledRenderToTexture = true;
mNativeExtensions.copyTexture = true;
mNativeExtensions.copyTexture3d = true;
mNativeExtensions.copyCompressedTexture = true;
......
......@@ -56,6 +56,28 @@ VkImageUsageFlags GetStagingBufferUsageFlags(vk::StagingUsage usage)
}
}
bool FindCompatibleMemory(const VkPhysicalDeviceMemoryProperties &memoryProperties,
const VkMemoryRequirements &memoryRequirements,
VkMemoryPropertyFlags requestedMemoryPropertyFlags,
VkMemoryPropertyFlags *memoryPropertyFlagsOut,
uint32_t *typeIndexOut)
{
for (size_t memoryIndex : angle::BitSet32<32>(memoryRequirements.memoryTypeBits))
{
ASSERT(memoryIndex < memoryProperties.memoryTypeCount);
if ((memoryProperties.memoryTypes[memoryIndex].propertyFlags &
requestedMemoryPropertyFlags) == requestedMemoryPropertyFlags)
{
*memoryPropertyFlagsOut = memoryProperties.memoryTypes[memoryIndex].propertyFlags;
*typeIndexOut = static_cast<uint32_t>(memoryIndex);
return true;
}
}
return false;
}
angle::Result FindAndAllocateCompatibleMemory(vk::Context *context,
const vk::MemoryProperties &memoryProperties,
VkMemoryPropertyFlags requestedMemoryPropertyFlags,
......@@ -334,6 +356,19 @@ void MemoryProperties::destroy()
mMemoryProperties = {};
}
bool MemoryProperties::hasLazilyAllocatedMemory() const
{
for (uint32_t typeIndex = 0; typeIndex < mMemoryProperties.memoryTypeCount; ++typeIndex)
{
const VkMemoryType &memoryType = mMemoryProperties.memoryTypes[typeIndex];
if ((memoryType.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) != 0)
{
return true;
}
}
return false;
}
angle::Result MemoryProperties::findCompatibleMemoryIndex(
Context *context,
const VkMemoryRequirements &memoryRequirements,
......@@ -347,41 +382,29 @@ angle::Result MemoryProperties::findCompatibleMemoryIndex(
// Not finding a valid memory pool means an out-of-spec driver, or internal error.
// TODO(jmadill): Determine if it is possible to cache indexes.
// TODO(jmadill): More efficient memory allocation.
for (size_t memoryIndex : angle::BitSet32<32>(memoryRequirements.memoryTypeBits))
{
ASSERT(memoryIndex < mMemoryProperties.memoryTypeCount);
if ((mMemoryProperties.memoryTypes[memoryIndex].propertyFlags &
requestedMemoryPropertyFlags) == requestedMemoryPropertyFlags)
if (FindCompatibleMemory(mMemoryProperties, memoryRequirements, requestedMemoryPropertyFlags,
memoryPropertyFlagsOut, typeIndexOut))
{
*memoryPropertyFlagsOut = mMemoryProperties.memoryTypes[memoryIndex].propertyFlags;
*typeIndexOut = static_cast<uint32_t>(memoryIndex);
return angle::Result::Continue;
}
}
// We did not find a compatible memory type, the Vulkan spec says the following -
// We did not find a compatible memory type. If the caller wanted a host visible memory, just
// return the memory index with fallback, guaranteed, memory flags.
if (requestedMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
{
// The Vulkan spec says the following -
// There must be at least one memory type with both the
// VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT and VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
// bits set in its propertyFlags
constexpr VkMemoryPropertyFlags fallbackMemoryPropertyFlags =
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
// If the caller wanted a host visible memory, just return the memory index
// with the fallback memory flags.
if (requestedMemoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)
if (FindCompatibleMemory(mMemoryProperties, memoryRequirements, fallbackMemoryPropertyFlags,
memoryPropertyFlagsOut, typeIndexOut))
{
for (size_t memoryIndex : angle::BitSet32<32>(memoryRequirements.memoryTypeBits))
{
if ((mMemoryProperties.memoryTypes[memoryIndex].propertyFlags &
fallbackMemoryPropertyFlags) == fallbackMemoryPropertyFlags)
{
*memoryPropertyFlagsOut = mMemoryProperties.memoryTypes[memoryIndex].propertyFlags;
*typeIndexOut = static_cast<uint32_t>(memoryIndex);
return angle::Result::Continue;
}
}
}
// TODO(jmadill): Add error message to error.
context->handleError(VK_ERROR_INCOMPATIBLE_DRIVER, __FILE__, ANGLE_FUNCTION, __LINE__);
......
......@@ -292,6 +292,7 @@ class MemoryProperties final : angle::NonCopyable
MemoryProperties();
void init(VkPhysicalDevice physicalDevice);
bool hasLazilyAllocatedMemory() const;
angle::Result findCompatibleMemoryIndex(Context *context,
const VkMemoryRequirements &memoryRequirements,
VkMemoryPropertyFlags requestedMemoryPropertyFlags,
......
......@@ -243,3 +243,7 @@
4371 VULKAN ANDROID : dEQP-GLES31.functional.shaders.implicit_conversions.es31.arithmetic.input_before_literal.add.int_to_vec3_vertex = FAIL
4371 VULKAN ANDROID : dEQP-GLES31.functional.shaders.implicit_conversions.es31.arithmetic.input_before_literal.add.int_to_uvec3_vertex = FAIL
4371 SWIFTSHADER : dEQP-GLES31.functional.shaders.implicit_conversions.* = FAIL
// Bug in gl.xml, aliasing glRenderbufferStorageMultisampleEXT and glRenderbufferStorageMultisample
// for GLES. See https://github.com/KhronosGroup/OpenGL-Registry/issues/413
4836 VULKAN : dEQP-GLES31.functional.debug.negative_coverage.get_error.buffer.renderbuffer_storage_multisample = FAIL
......@@ -639,3 +639,7 @@
// Fails on SwANGLE bots
4418 SWIFTSHADER : dEQP-GLES3.functional.negative_api.buffer.framebuffer_texture_layer = FAIL
// Bug in gl.xml, aliasing glRenderbufferStorageMultisampleEXT and glRenderbufferStorageMultisample
// for GLES. See https://github.com/KhronosGroup/OpenGL-Registry/issues/413
4836 VULKAN : dEQP-GLES3.functional.negative_api.buffer.renderbuffer_storage_multisample = FAIL
......@@ -118,3 +118,8 @@
// New failures with latest dEQP roll (2020-04-28)
4593 SWIFTSHADER : KHR-GLES31.core.nearest_edge.offset_left = FAIL
// Bug in gl.xml, aliasing glRenderbufferStorageMultisampleEXT and glRenderbufferStorageMultisample
// for GLES. See https://github.com/KhronosGroup/OpenGL-Registry/issues/413
4836 VULKAN : KHR-GLES31.core.texture_storage_multisample.APIDependencies.renderbuffer_storage_multisample_invalid_samples_argument_for_integer_internalformats = FAIL
4836 VULKAN : KHR-GLES31.core.texture_storage_multisample.APIDependencies.renderbuffer_storage_multisample_invalid_samples_argument_for_noninteger_internalformats = FAIL
......@@ -25,34 +25,15 @@ class CopyTexImageTest : public ANGLETest
void testSetUp() override
{
constexpr char kVS[] =
"precision highp float;\n"
"attribute vec4 position;\n"
"varying vec2 texcoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
" texcoord = (position.xy * 0.5) + 0.5;\n"
"}\n";
constexpr char kFS[] =
"precision highp float;\n"
"uniform sampler2D tex;\n"
"varying vec2 texcoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = texture2D(tex, texcoord);\n"
"}\n";
mTextureProgram = CompileProgram(kVS, kFS);
mTextureProgram =
CompileProgram(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
if (mTextureProgram == 0)
{
FAIL() << "shader compilation failed.";
}
mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");
mTextureUniformLocation =
glGetUniformLocation(mTextureProgram, essl1_shaders::Texture2DUniform());
ASSERT_GL_NO_ERROR();
}
......@@ -99,7 +80,7 @@ class CopyTexImageTest : public ANGLETest
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(mTextureUniformLocation, 0);
drawQuad(mTextureProgram, "position", 0.5f);
drawQuad(mTextureProgram, essl1_shaders::PositionAttrib(), 0.5f);
// Expect that the rendered quad has the same color as the source texture
EXPECT_PIXEL_NEAR(xs, ys, data[0], data[1], data[2], data[3], 1.0);
......@@ -891,13 +872,8 @@ TEST_P(CopyTexImageTestES3, 3DSubImageDrawMismatchedTextureTypes)
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST(CopyTexImageTest,
ES2_D3D9(),
ES2_D3D11(),
ANGLE_ALL_TEST_PLATFORMS_ES2,
ES2_D3D11_PRESENT_PATH_FAST(),
ES2_METAL(),
ES2_OPENGL(),
ES2_OPENGLES(),
ES2_VULKAN(),
ES3_VULKAN(),
WithEmulateCopyTexImage2DFromRenderbuffers(ES2_OPENGL()),
WithEmulateCopyTexImage2DFromRenderbuffers(ES2_OPENGLES()));
......
......@@ -13,46 +13,6 @@ using namespace angle;
namespace
{
constexpr char kBasicVertexShader[] =
R"(attribute vec3 position;
void main()
{
gl_Position = vec4(position, 1);
})";
constexpr char kGreenFragmentShader[] =
R"(void main()
{
gl_FragColor = vec4(0, 1, 0, 1);
})";
constexpr char kRedFragmentShader[] =
R"(void main()
{
gl_FragColor = vec4(1, 0, 0, 1);
})";
constexpr char kVS[] =
"precision highp float;\n"
"attribute vec4 position;\n"
"varying vec2 texcoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
" texcoord = (position.xy * 0.5) + 0.5;\n"
"}\n";
constexpr char kFS[] =
"precision highp float;\n"
"uniform sampler2D tex;\n"
"varying vec2 texcoord;\n"
"\n"
"void main()\n"
"{\n"
" gl_FragColor = texture2D(tex, texcoord);\n"
"}\n";
class MultisampledRenderToTextureTest : public ANGLETest
{
protected:
......@@ -72,16 +32,18 @@ class MultisampledRenderToTextureTest : public ANGLETest
void setupCopyTexProgram()
{
mCopyTextureProgram.makeRaster(kVS, kFS);
mCopyTextureProgram.makeRaster(essl1_shaders::vs::Texture2D(),
essl1_shaders::fs::Texture2D());
ASSERT_GL_TRUE(mCopyTextureProgram.valid());
mCopyTextureUniformLocation = glGetUniformLocation(mCopyTextureProgram, "tex");
mCopyTextureUniformLocation =
glGetUniformLocation(mCopyTextureProgram, essl1_shaders::Texture2DUniform());
ASSERT_GL_NO_ERROR();
}
void verifyResults(GLuint texture,
GLubyte data[4],
const GLColor expected,
GLint fboSize,
GLint xs,
GLint ys,
......@@ -97,14 +59,14 @@ class MultisampledRenderToTextureTest : public ANGLETest
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(mCopyTextureUniformLocation, 0);
drawQuad(mCopyTextureProgram, "position", 0.5f);
drawQuad(mCopyTextureProgram, essl1_shaders::PositionAttrib(), 0.5f);
// Expect that the rendered quad has the same color as the source texture
EXPECT_PIXEL_NEAR(xs, ys, data[0], data[1], data[2], data[3], 1.0);
EXPECT_PIXEL_NEAR(xs, ye - 1, data[0], data[1], data[2], data[3], 1.0);
EXPECT_PIXEL_NEAR(xe - 1, ys, data[0], data[1], data[2], data[3], 1.0);
EXPECT_PIXEL_NEAR(xe - 1, ye - 1, data[0], data[1], data[2], data[3], 1.0);
EXPECT_PIXEL_NEAR((xs + xe) / 2, (ys + ye) / 2, data[0], data[1], data[2], data[3], 1.0);
EXPECT_PIXEL_COLOR_NEAR(xs, ys, expected, 1.0);
EXPECT_PIXEL_COLOR_NEAR(xs, ye - 1, expected, 1.0);
EXPECT_PIXEL_COLOR_NEAR(xe - 1, ys, expected, 1.0);
EXPECT_PIXEL_COLOR_NEAR(xe - 1, ye - 1, expected, 1.0);
EXPECT_PIXEL_COLOR_NEAR((xs + xe) / 2, (ys + ye) / 2, expected, 1.0);
}
void clearAndDrawQuad(GLuint program, GLsizei viewportWidth, GLsizei viewportHeight)
......@@ -114,7 +76,8 @@ class MultisampledRenderToTextureTest : public ANGLETest
glViewport(0, 0, viewportWidth, viewportHeight);
ASSERT_GL_NO_ERROR();
drawQuad(program, "position", 0.0f);
drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
ASSERT_GL_NO_ERROR();
}
GLProgram mCopyTextureProgram;
......@@ -290,13 +253,24 @@ TEST_P(MultisampledRenderToTextureTest, FramebufferCompleteness)
texture, 0, 4);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
GLsizei maxSamples = 0;
glGetIntegerv(GL_MAX_SAMPLES, &maxSamples);
GLRenderbuffer renderbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 8, GL_DEPTH_COMPONENT16, 64, 64);
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, maxSamples, GL_DEPTH_COMPONENT16, 64, 64);
ASSERT_GL_NO_ERROR();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
if (maxSamples > 4)
{
EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
glCheckFramebufferStatus(GL_FRAMEBUFFER));
}
else
{
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
}
}
// Draw test with color attachment only.
......@@ -304,10 +278,10 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
// Set up texture and bind to FBO
GLsizei size = 6;
constexpr GLsizei kSize = 6;
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
GLFramebuffer FBO;
......@@ -317,14 +291,14 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Set viewport and clear to black
glViewport(0, 0, size, size);
glViewport(0, 0, kSize, kSize);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// Set up Green square program
ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
glUseProgram(program);
GLint positionLocation = glGetAttribLocation(program, "position");
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
setupQuadVertexBuffer(0.5f, 0.5f);
......@@ -336,12 +310,12 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);
// Set up Red square program
ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program2);
GLint positionLocation2 = glGetAttribLocation(program2, "position");
GLint positionLocation2 = glGetAttribLocation(program2, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation2);
setupQuadVertexBuffer(0.5f, 0.75f);
......@@ -352,7 +326,7 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::red);
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
......@@ -362,11 +336,11 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorAttachmentMultisampleDrawTest)
TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
GLsizei size = 6;
constexpr GLsizei kSize = 6;
// create complete framebuffer with depth buffer
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
ASSERT_GL_NO_ERROR();
GLFramebuffer FBO;
......@@ -376,23 +350,23 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
GLRenderbuffer renderbuffer;
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, size, size);
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, kSize, kSize);
ASSERT_GL_NO_ERROR();
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Set viewport and clear framebuffer
glViewport(0, 0, size, size);
glViewport(0, 0, kSize, kSize);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClearDepthf(0.5f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Draw first green square
ANGLE_GL_PROGRAM(program, kBasicVertexShader, kGreenFragmentShader);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_GREATER);
glUseProgram(program);
GLint positionLocation = glGetAttribLocation(program, "position");
GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation);
setupQuadVertexBuffer(0.8f, 0.5f);
......@@ -404,12 +378,12 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);
// Draw red square behind green square
ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program2);
GLint positionLocation2 = glGetAttribLocation(program2, "position");
GLint positionLocation2 = glGetAttribLocation(program2, essl1_shaders::PositionAttrib());
ASSERT_NE(-1, positionLocation2);
setupQuadVertexBuffer(0.7f, 1.0f);
......@@ -420,23 +394,21 @@ TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
glDisable(GL_DEPTH_TEST);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);
glDisableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// Read pixels with pack buffer. ES3+.
TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
TEST_P(MultisampledRenderToTextureES3Test, ReadPixelsTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
// PBO only available ES3 and above
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
GLsizei size = 6;
constexpr GLsizei kSize = 6;
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size, size);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
ASSERT_GL_NO_ERROR();
GLFramebuffer FBO;
......@@ -446,7 +418,7 @@ TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
// Set viewport and clear to red
glViewport(0, 0, size, size);
glViewport(0, 0, kSize, kSize);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ASSERT_GL_NO_ERROR();
......@@ -454,8 +426,8 @@ TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
// Bind Pack Pixel Buffer and read to it
GLBuffer PBO;
glBindBuffer(GL_PIXEL_PACK_BUFFER, PBO);
glBufferData(GL_PIXEL_PACK_BUFFER, 4 * size * size, nullptr, GL_STATIC_DRAW);
glReadPixels(0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBufferData(GL_PIXEL_PACK_BUFFER, 4 * kSize * kSize, nullptr, GL_STATIC_DRAW);
glReadPixels(0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE, 0);
ASSERT_GL_NO_ERROR();
// Retrieving pixel color
......@@ -470,15 +442,15 @@ TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
}
// CopyTexImage from a multisampled texture functionality test.
TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexImageTest)
TEST_P(MultisampledRenderToTextureTest, CopyTexImageTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
GLsizei size = 16;
constexpr GLsizei kSize = 16;
setupCopyTexProgram();
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Disable mipmapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
......@@ -501,25 +473,27 @@ TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexImageTest)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, size, size, 0);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, kSize, kSize, 0);
ASSERT_GL_NO_ERROR();
GLubyte expected[4] = {64, 255, 191, 255};
verifyResults(copyToTex, expected, size, 0, 0, size, size);
verifyResults(copyToTex, {64, 255, 191, 255}, kSize, 0, 0, kSize, kSize);
}
// CopyTexSubImage from a multisampled texture functionality test.
TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexSubImageTest)
TEST_P(MultisampledRenderToTextureTest, CopyTexSubImageTest)
{
// Fails on Pixel 2. http://anglebug.com/4906
ANGLE_SKIP_TEST_IF(IsAndroid());
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
GLsizei size = 16;
constexpr GLsizei kSize = 16;
setupCopyTexProgram();
GLTexture texture;
// Create texture in copyFBO0 with color (.25, 1, .75, .5)
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Disable mipmapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
......@@ -538,7 +512,7 @@ TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexSubImageTest)
// Create texture in copyFBO[1] with color (1, .75, .5, .25)
GLTexture texture1;
glBindTexture(GL_TEXTURE_2D, texture1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// Disable mipmapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
......@@ -564,50 +538,48 @@ TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexSubImageTest)
// copyFBO0 -> copyToTex
// copyToTex should hold what was originally in copyFBO0 : (.25, 1, .75, .5)
glBindFramebuffer(GL_FRAMEBUFFER, copyFBO0);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, size, size, 0);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, kSize, kSize, 0);
ASSERT_GL_NO_ERROR();
GLubyte expected0[4] = {64, 255, 191, 255};
verifyResults(copyToTex, expected0, size, 0, 0, size, size);
const GLColor expected0(64, 255, 191, 255);
verifyResults(copyToTex, expected0, kSize, 0, 0, kSize, kSize);
// copyFBO[1] - copySubImage -> copyToTex
// copyToTex should have subportion what was in copyFBO[1] : (1, .75, .5, .25)
// The rest should still be untouched: (.25, 1, .75, .5)
GLint half = size / 2;
GLint half = kSize / 2;
glBindFramebuffer(GL_FRAMEBUFFER, copyFBO1);
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, half, half, half, half, half, half);
ASSERT_GL_NO_ERROR();
GLubyte expected1[4] = {255, 191, 127, 255};
verifyResults(copyToTex, expected1, size, half, half, size, size);
const GLColor expected1(255, 191, 127, 255);
verifyResults(copyToTex, expected1, kSize, half, half, kSize, kSize);
// Verify rest is untouched
verifyResults(copyToTex, expected0, size, 0, 0, half, half);
verifyResults(copyToTex, expected0, size, 0, half, half, size);
verifyResults(copyToTex, expected0, size, half, 0, size, half);
verifyResults(copyToTex, expected0, kSize, 0, 0, half, half);
verifyResults(copyToTex, expected0, kSize, 0, half, half, kSize);
verifyResults(copyToTex, expected0, kSize, half, 0, kSize, half);
}
// BlitFramebuffer functionality test. ES3+.
TEST_P(MultisampledRenderToTextureES3Test, MultisampleBlitFramebufferTest)
TEST_P(MultisampledRenderToTextureES3Test, BlitFramebufferTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
// blitFramebuffer only available ES3 and above
ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
GLsizei size = 16;
constexpr GLsizei kSize = 16;
// Create multisampled framebuffer to use as source.
GLRenderbuffer depthMS;
glBindRenderbuffer(GL_RENDERBUFFER, depthMS.get());
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT24, size, size);
glBindRenderbuffer(GL_RENDERBUFFER, depthMS);
glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT24, kSize, kSize);
GLTexture colorMS;
glBindTexture(GL_TEXTURE_2D, colorMS);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer fboMS;
glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS.get());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS);
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
colorMS, 0, 4);
ASSERT_GL_NO_ERROR();
......@@ -623,7 +595,7 @@ TEST_P(MultisampledRenderToTextureES3Test, MultisampleBlitFramebufferTest)
ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_EQUAL);
drawQuad(drawRed.get(), essl1_shaders::PositionAttrib(), 0.0f);
drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);
ASSERT_GL_NO_ERROR();
// Create single sampled framebuffer to use as dest.
......@@ -631,61 +603,42 @@ TEST_P(MultisampledRenderToTextureES3Test, MultisampleBlitFramebufferTest)
glBindFramebuffer(GL_FRAMEBUFFER, fboSS);
GLTexture colorSS;
glBindTexture(GL_TEXTURE_2D, colorSS);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorSS, 0);
ASSERT_GL_NO_ERROR();
// Bind MS to READ as SS is already bound to DRAW.
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboMS.get());
glBlitFramebuffer(0, 0, size, size, 0, 0, size, size, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboMS);
glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
ASSERT_GL_NO_ERROR();
// Bind SS to READ so we can readPixels from it
glBindFramebuffer(GL_FRAMEBUFFER, fboSS.get());
glBindFramebuffer(GL_FRAMEBUFFER, fboSS);
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(size - 1, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(0, size - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(size - 1, size - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::red);
ASSERT_GL_NO_ERROR();
}
// GenerateMipmap functionality test
TEST_P(MultisampledRenderToTextureTest, MultisampleGenerateMipmapTest)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
GLsizei size = 64;
// Vertex Shader source
constexpr char kVS[] = R"(attribute vec4 position;
varying vec2 vTexCoord;
void main()
{
gl_Position = position;
vTexCoord = (position.xy * 0.5) + 0.5;
})";
// Fragment Shader source
constexpr char kFS[] = R"(precision mediump float;
uniform sampler2D uTexture;
varying vec2 vTexCoord;
void main()
TEST_P(MultisampledRenderToTextureTest, GenerateMipmapTest)
{
gl_FragColor = texture2D(uTexture, vTexCoord);
})";
// Fails on Pixel 2. http://anglebug.com/4906
ANGLE_SKIP_TEST_IF(IsAndroid());
GLProgram m2DProgram;
m2DProgram.makeRaster(kVS, kFS);
ASSERT_GL_TRUE(m2DProgram.valid());
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
constexpr GLsizei kSize = 64;
ASSERT_GL_NO_ERROR();
setupCopyTexProgram();
glUseProgram(mCopyTextureProgram);
// Initialize texture with blue
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kSize, kSize, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
......@@ -696,7 +649,7 @@ void main()
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, size, size);
glViewport(0, 0, kSize, kSize);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_GL_NO_ERROR();
......@@ -707,32 +660,83 @@ void main()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
// Now draw the texture to various different sized areas.
clearAndDrawQuad(m2DProgram, size, size);
EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::blue);
clearAndDrawQuad(mCopyTextureProgram, kSize, kSize);
EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::blue);
// Use mip level 1
clearAndDrawQuad(m2DProgram, size / 2, size / 2);
EXPECT_PIXEL_COLOR_EQ(size / 4, size / 4, GLColor::blue);
clearAndDrawQuad(mCopyTextureProgram, kSize / 2, kSize / 2);
EXPECT_PIXEL_COLOR_EQ(kSize / 4, kSize / 4, GLColor::blue);
// Use mip level 2
clearAndDrawQuad(m2DProgram, size / 4, size / 4);
EXPECT_PIXEL_COLOR_EQ(size / 8, size / 8, GLColor::blue);
clearAndDrawQuad(mCopyTextureProgram, kSize / 4, kSize / 4);
EXPECT_PIXEL_COLOR_EQ(kSize / 8, kSize / 8, GLColor::blue);
ASSERT_GL_NO_ERROR();
}
// Draw, copy, then blend. The copy will make sure an implicit resolve happens. Regardless, the
// following draw should retain the data written by the first draw command.
TEST_P(MultisampledRenderToTextureTest, DrawCopyThenBlend)
{
ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
constexpr GLsizei kSize = 64;
setupCopyTexProgram();
// Create multisampled framebuffer to draw into
GLTexture colorMS;
glBindTexture(GL_TEXTURE_2D, colorMS);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GLFramebuffer fboMS;
glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
colorMS, 0, 4);
ASSERT_GL_NO_ERROR();
// Draw red into the multisampled color buffer.
ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
glUseProgram(drawColor);
GLint colorUniformLocation =
glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
ASSERT_NE(colorUniformLocation, -1);
glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.5f);
ASSERT_GL_NO_ERROR();
// Create a texture and copy into it.
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture);
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, kSize, kSize, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Draw again into the framebuffer, this time blending. This tests that the framebuffer's data,
// residing in the single-sampled texture, is available to the multisampled intermediate image
// for blending.
// Blend half-transparent green into the multisampled color buffer.
glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 0.5f);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.5f);
ASSERT_GL_NO_ERROR();
// Verify that the texture is now yellow
const GLColor kExpected(127, 127, 0, 191);
EXPECT_PIXEL_COLOR_NEAR(0, 0, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(kSize - 1, 0, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(0, kSize - 1, kExpected, 1);
EXPECT_PIXEL_COLOR_NEAR(kSize - 1, kSize - 1, kExpected, 1);
// For completeness, verify that the texture used as copy target is red.
const GLColor expectedCopyResult(255, 0, 0, 255);
verifyResults(texture, expectedCopyResult, kSize, 0, 0, kSize, kSize);
ASSERT_GL_NO_ERROR();
}
ANGLE_INSTANTIATE_TEST(MultisampledRenderToTextureTest,
ES2_D3D9(),
ES2_D3D11(),
ES3_D3D11(),
ES2_OPENGL(),
ES3_OPENGL(),
ES2_OPENGLES(),
ES3_OPENGLES(),
ES2_VULKAN(),
ES3_VULKAN());
ANGLE_INSTANTIATE_TEST(MultisampledRenderToTextureES3Test,
ES3_D3D11(),
ES3_OPENGL(),
ES3_OPENGLES(),
ES3_VULKAN());
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(MultisampledRenderToTextureTest);
ANGLE_INSTANTIATE_TEST_ES3(MultisampledRenderToTextureES3Test);
} // namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment