Commit b16d69c3 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Add support for surface multisampling

A multisample image is created for the surface if multisampling is enabled. Prior to present, this multisample image is resolved into the swapchain image. FramebufferVk::readPixelsImpl similarly has got the ability to resolve the region of interest into a temporary image prior to readback. Tests are added to render a point, line and a triangle on a 4x multisampled surface. Bug: angleproject:3204 Change-Id: I34aca502fa1918b5cbf000ff11521c350372e051 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1610188Reviewed-by: 's avatarShahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
parent cd3011fb
......@@ -23,8 +23,6 @@ namespace gl
struct Extensions;
typedef std::set<GLuint> SupportedSampleSet;
struct TextureCaps
{
TextureCaps();
......
......@@ -466,6 +466,8 @@ using UniformBuffersArray = std::array<T, IMPLEMENTATION_MAX_UNIFORM_BUFFER_BIND
using ImageUnitMask = angle::BitSet<IMPLEMENTATION_MAX_IMAGE_UNITS>;
using SupportedSampleSet = std::set<GLuint>;
// OffsetBindingPointer.getSize() returns the size specified by the user, which may be larger than
// the size of the bound buffer. This function reduces the returned size to fit the bound buffer if
// necessary. Returns 0 if no buffer is bound or if integer overflow occurs.
......
......@@ -685,6 +685,16 @@ void ContextVk::updateColorMask(const gl::BlendState &blendState)
framebufferVk->getEmulatedAlphaAttachmentMask());
}
void ContextVk::updateSampleMask(const gl::State &glState)
{
for (uint32_t maskNumber = 0; maskNumber < glState.getMaxSampleMaskWords(); ++maskNumber)
{
static_assert(sizeof(uint32_t) == sizeof(GLbitfield), "Vulkan assumes 32-bit sample masks");
uint32_t mask = glState.isSampleMaskEnabled() ? glState.getSampleMaskWord(maskNumber) : 0;
mGraphicsPipelineDesc->updateSampleMask(&mGraphicsPipelineTransition, maskNumber, mask);
}
}
void ContextVk::updateViewport(FramebufferVk *framebufferVk,
const gl::Rectangle &viewport,
float nearPlane,
......@@ -789,14 +799,43 @@ angle::Result ContextVk::syncState(const gl::Context *context,
updateColorMask(glState.getBlendState());
break;
case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_COVERAGE_ENABLED:
mGraphicsPipelineDesc->updateAlphaToCoverageEnable(
&mGraphicsPipelineTransition, glState.isSampleAlphaToCoverageEnabled());
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE_ENABLED:
// TODO(syoussefi): glSampleCoverage and `GL_SAMPLE_COVERAGE` have a similar
// behavior to alphaToCoverage, without native support in Vulkan. Sample coverage
// results in a mask that's applied *on top of* alphaToCoverage. More importantly,
// glSampleCoverage can choose to invert the applied mask; a feature that's not
// easily emulatable. For example, say there are 4 samples {0, 1, 2, 3} and
// alphaToCoverage (both in GL and Vulkan, as well as sampleCoverage in GL) is
// implemented such that the alpha value selects the set of samples
// {0, ..., round(alpha * 4)}. With glSampleCoverage, an application can blend two
// object LODs as such the following, covering all samples in a pixel:
//
// glSampleCoverage(0.5, GL_FALSE); // covers samples {0, 1}
// drawLOD0();
// glSampleCoverage(0.5, GL_TRUE); // covers samples {2, 3}
// drawLOD1();
//
// In Vulkan, it's not possible to restrict drawing to samples {2, 3} through
// alphaToCoverage alone.
//
// One way to acheive this behavior is to modify the shader to output to
// gl_SampleMask with values we emulate for sample coverage, taking inversion
// into account.
//
// http://anglebug.com/3204
break;
case gl::State::DIRTY_BIT_SAMPLE_COVERAGE:
// TODO(syoussefi): See DIRTY_BIT_SAMPLE_COVERAGE_ENABLED.
// http://anglebug.com/3204
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK_ENABLED:
updateSampleMask(glState);
break;
case gl::State::DIRTY_BIT_SAMPLE_MASK:
updateSampleMask(glState);
break;
case gl::State::DIRTY_BIT_DEPTH_TEST_ENABLED:
mGraphicsPipelineDesc->updateDepthTestEnabled(&mGraphicsPipelineTransition,
......@@ -919,6 +958,9 @@ angle::Result ContextVk::syncState(const gl::Context *context,
updateViewport(mDrawFramebuffer, glState.getViewport(), glState.getNearPlane(),
glState.getFarPlane(), isViewportFlipEnabledForDrawFBO());
updateColorMask(glState.getBlendState());
updateSampleMask(glState);
mGraphicsPipelineDesc->updateRasterizationSamples(&mGraphicsPipelineTransition,
mDrawFramebuffer->getSamples());
mGraphicsPipelineDesc->updateCullMode(&mGraphicsPipelineTransition,
glState.getRasterizerState());
updateScissor(glState);
......@@ -987,8 +1029,16 @@ angle::Result ContextVk::syncState(const gl::Context *context,
case gl::State::DIRTY_BIT_IMAGE_BINDINGS:
break;
case gl::State::DIRTY_BIT_MULTISAMPLING:
// TODO(syoussefi): this should configure the pipeline to render as if
// single-sampled, and write the results to all samples of a pixel regardless of
// coverage. See EXT_multisample_compatibility. http://anglebug.com/3204
break;
case gl::State::DIRTY_BIT_SAMPLE_ALPHA_TO_ONE:
// TODO(syoussefi): this is part of EXT_multisample_compatibility. The alphaToOne
// Vulkan feature should be enabled to support this extension.
// http://anglebug.com/3204
mGraphicsPipelineDesc->updateAlphaToOneEnable(&mGraphicsPipelineTransition,
glState.isSampleAlphaToOneEnabled());
break;
case gl::State::DIRTY_BIT_COVERAGE_MODULATION:
break;
......
......@@ -207,6 +207,7 @@ class ContextVk : public ContextImpl, public vk::Context, public vk::CommandBuff
gl::TextureType type,
gl::Texture **textureOut);
void updateColorMask(const gl::BlendState &blendState);
void updateSampleMask(const gl::State &glState);
void handleError(VkResult errorCode,
const char *file,
......
......@@ -1169,6 +1169,8 @@ angle::Result FramebufferVk::readPixelsImpl(ContextVk *contextVk,
{
TRACE_EVENT0("gpu.angle", "FramebufferVk::readPixelsImpl");
RendererVk *renderer = contextVk->getRenderer();
ANGLE_TRY(renderTarget->ensureImageInitialized(contextVk));
vk::CommandBuffer *commandBuffer = nullptr;
......@@ -1186,6 +1188,56 @@ angle::Result FramebufferVk::readPixelsImpl(ContextVk *contextVk,
readFormat = &GetDepthStencilImageToBufferFormat(*readFormat, copyAspectFlags);
}
size_t level = renderTarget->getLevelIndex();
size_t layer = renderTarget->getLayerIndex();
VkOffset3D srcOffset = {area.x, area.y, 0};
VkExtent3D srcExtent = {static_cast<uint32_t>(area.width), static_cast<uint32_t>(area.height),
1};
// If the source image is multisampled, we need to resolve it into a temporary image before
// performing a readback.
bool isMultisampled = srcImage->getSamples() > 1;
vk::Scoped<vk::ImageHelper> resolvedImage(contextVk->getDevice());
if (isMultisampled)
{
ANGLE_TRY(resolvedImage.get().init2DStaging(
contextVk, renderer->getMemoryProperties(), gl::Extents(area.width, area.height, 1),
srcImage->getFormat(),
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1));
resolvedImage.get().updateQueueSerial(renderer->getCurrentQueueSerial());
// TODO(syoussefi): resolve only works on color images (not depth/stencil). If readback
// on multisampled depth/stencil image is done, we would need a different path. One
// possible solution would be a compute shader that directly reads from the multisampled
// image, performs the resolve and outputs to the buffer in one go.
// http://anglebug.com/3200
ASSERT(copyAspectFlags == VK_IMAGE_ASPECT_COLOR_BIT);
VkImageResolve resolveRegion = {};
resolveRegion.srcSubresource.aspectMask = copyAspectFlags;
resolveRegion.srcSubresource.mipLevel = level;
resolveRegion.srcSubresource.baseArrayLayer = layer;
resolveRegion.srcSubresource.layerCount = 1;
resolveRegion.srcOffset = srcOffset;
resolveRegion.dstSubresource.aspectMask = copyAspectFlags;
resolveRegion.dstSubresource.mipLevel = 0;
resolveRegion.dstSubresource.baseArrayLayer = 0;
resolveRegion.dstSubresource.layerCount = 1;
resolveRegion.dstOffset = {};
resolveRegion.extent = srcExtent;
srcImage->resolve(&resolvedImage.get(), resolveRegion, commandBuffer);
resolvedImage.get().changeLayout(copyAspectFlags, vk::ImageLayout::TransferSrc,
commandBuffer);
// Make the resolved image the target of buffer copy.
srcImage = &resolvedImage.get();
level = 0;
layer = 0;
srcOffset = {0, 0, 0};
}
VkBuffer bufferHandle = VK_NULL_HANDLE;
uint8_t *readPixelBuffer = nullptr;
VkDeviceSize stagingOffset = 0;
......@@ -1195,19 +1247,15 @@ angle::Result FramebufferVk::readPixelsImpl(ContextVk *contextVk,
&stagingOffset, nullptr));
VkBufferImageCopy region = {};
region.bufferImageHeight = area.height;
region.bufferImageHeight = srcExtent.height;
region.bufferOffset = stagingOffset;
region.bufferRowLength = area.width;
region.imageExtent.width = area.width;
region.imageExtent.height = area.height;
region.imageExtent.depth = 1;
region.imageOffset.x = area.x;
region.imageOffset.y = area.y;
region.imageOffset.z = 0;
region.bufferRowLength = srcExtent.width;
region.imageExtent = srcExtent;
region.imageOffset = srcOffset;
region.imageSubresource.aspectMask = copyAspectFlags;
region.imageSubresource.baseArrayLayer = renderTarget->getLayerIndex();
region.imageSubresource.baseArrayLayer = layer;
region.imageSubresource.layerCount = 1;
region.imageSubresource.mipLevel = renderTarget->getLevelIndex();
region.imageSubresource.mipLevel = level;
commandBuffer->copyImageToBuffer(srcImage->getImage(), srcImage->getCurrentLayout(),
bufferHandle, 1, &region);
......
......@@ -262,6 +262,15 @@ void SecondaryCommandBuffer::executeCommands(VkCommandBuffer cmdBuffer)
params->queryCount);
break;
}
case CommandID::ResolveImage:
{
const ResolveImageParams *params =
getParamPtr<ResolveImageParams>(currentCommand);
vkCmdResolveImage(cmdBuffer, params->srcImage,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, params->dstImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &params->region);
break;
}
case CommandID::SetEvent:
{
const SetEventParams *params = getParamPtr<SetEventParams>(currentCommand);
......
......@@ -56,6 +56,7 @@ enum class CommandID : uint16_t
PushConstants,
ResetEvent,
ResetQueryPool,
ResolveImage,
SetEvent,
WaitEvents,
WriteTimestamp,
......@@ -279,6 +280,14 @@ struct ResetQueryPoolParams
};
VERIFY_4_BYTE_ALIGNMENT(ResetQueryPoolParams)
struct ResolveImageParams
{
VkImage srcImage;
VkImage dstImage;
VkImageResolve region;
};
VERIFY_4_BYTE_ALIGNMENT(ResolveImageParams)
struct BeginQueryParams
{
VkQueryPool queryPool;
......@@ -364,7 +373,7 @@ class SecondaryCommandBuffer final : angle::NonCopyable
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
const VkImageBlit *pRegions,
const VkImageBlit *regions,
VkFilter filter);
void clearAttachments(uint32_t attachmentCount,
......@@ -448,6 +457,13 @@ class SecondaryCommandBuffer final : angle::NonCopyable
void resetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
void resolveImage(const Image &srcImage,
VkImageLayout srcImageLayout,
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
const VkImageResolve *regions);
void setEvent(VkEvent event, VkPipelineStageFlags stageMask);
void waitEvents(uint32_t eventCount,
......@@ -676,7 +692,7 @@ ANGLE_INLINE void SecondaryCommandBuffer::blitImage(const Image &srcImage,
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
const VkImageBlit *pRegions,
const VkImageBlit *regions,
VkFilter filter)
{
// Currently ANGLE uses limited params so verify those assumptions and update if they change
......@@ -687,7 +703,7 @@ ANGLE_INLINE void SecondaryCommandBuffer::blitImage(const Image &srcImage,
paramStruct->srcImage = srcImage.getHandle();
paramStruct->dstImage = dstImage.getHandle();
paramStruct->filter = filter;
paramStruct->region = pRegions[0];
paramStruct->region = regions[0];
}
ANGLE_INLINE void SecondaryCommandBuffer::clearAttachments(uint32_t attachmentCount,
......@@ -934,6 +950,23 @@ ANGLE_INLINE void SecondaryCommandBuffer::resetQueryPool(VkQueryPool queryPool,
paramStruct->queryCount = queryCount;
}
ANGLE_INLINE void SecondaryCommandBuffer::resolveImage(const Image &srcImage,
VkImageLayout srcImageLayout,
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
const VkImageResolve *regions)
{
// Currently ANGLE uses limited params so verify those assumptions and update if they change.
ASSERT(srcImageLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
ASSERT(dstImageLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
ASSERT(regionCount == 1);
ResolveImageParams *paramStruct = initCommand<ResolveImageParams>(CommandID::ResolveImage);
paramStruct->srcImage = srcImage.getHandle();
paramStruct->dstImage = dstImage.getHandle();
paramStruct->region = regions[0];
}
ANGLE_INLINE void SecondaryCommandBuffer::setEvent(VkEvent event, VkPipelineStageFlags stageMask)
{
SetEventParams *paramStruct = initCommand<SetEventParams>(CommandID::SetEvent);
......
......@@ -25,6 +25,15 @@ namespace rx
namespace
{
GLint GetSampleCount(DisplayVk *displayVk, const egl::Config *config)
{
GLint samples = 1;
if (config->sampleBuffers && config->samples > 1)
{
samples = config->samples;
}
return samples;
}
VkPresentModeKHR GetDesiredPresentMode(const std::vector<VkPresentModeKHR> &presentModes,
EGLint interval)
......@@ -88,7 +97,8 @@ OffscreenSurfaceVk::AttachmentImage::~AttachmentImage() = default;
angle::Result OffscreenSurfaceVk::AttachmentImage::initialize(DisplayVk *displayVk,
EGLint width,
EGLint height,
const vk::Format &vkFormat)
const vk::Format &vkFormat,
GLint samples)
{
RendererVk *renderer = displayVk->getRenderer();
......@@ -99,7 +109,7 @@ angle::Result OffscreenSurfaceVk::AttachmentImage::initialize(DisplayVk *display
gl::Extents extents(std::max(static_cast<int>(width), 1), std::max(static_cast<int>(height), 1),
1);
ANGLE_TRY(image.init(displayVk, gl::TextureType::_2D, extents, vkFormat, 1, usage, 1, 1));
ANGLE_TRY(image.init(displayVk, gl::TextureType::_2D, extents, vkFormat, samples, usage, 1, 1));
VkMemoryPropertyFlags flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
ANGLE_TRY(image.initMemory(displayVk, renderer->getMemoryProperties(), flags));
......@@ -145,16 +155,19 @@ angle::Result OffscreenSurfaceVk::initializeImpl(DisplayVk *displayVk)
RendererVk *renderer = displayVk->getRenderer();
const egl::Config *config = mState.config;
GLint samples = GetSampleCount(displayVk, mState.config);
ANGLE_VK_CHECK(displayVk, samples > 0, VK_ERROR_INITIALIZATION_FAILED);
if (config->renderTargetFormat != GL_NONE)
{
ANGLE_TRY(mColorAttachment.initialize(displayVk, mWidth, mHeight,
renderer->getFormat(config->renderTargetFormat)));
ANGLE_TRY(mColorAttachment.initialize(
displayVk, mWidth, mHeight, renderer->getFormat(config->renderTargetFormat), samples));
}
if (config->depthStencilFormat != GL_NONE)
{
ANGLE_TRY(mDepthStencilAttachment.initialize(
displayVk, mWidth, mHeight, renderer->getFormat(config->depthStencilFormat)));
displayVk, mWidth, mHeight, renderer->getFormat(config->depthStencilFormat), samples));
}
return angle::Result::Continue;
......@@ -352,6 +365,9 @@ WindowSurfaceVk::WindowSurfaceVk(const egl::SurfaceState &surfaceState,
mCurrentSwapHistoryIndex(0)
{
mDepthStencilRenderTarget.init(&mDepthStencilImage, &mDepthStencilImageView, 0, 0, nullptr);
// 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, &mColorImageViewMS, 0, 0, nullptr);
}
WindowSurfaceVk::~WindowSurfaceVk()
......@@ -569,6 +585,29 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
ANGLE_VK_TRY(displayVk,
vkGetSwapchainImagesKHR(device, mSwapchain, &imageCount, swapchainImages.data()));
// If multisampling is enabled, create a multisampled image which gets resolved just prior to
// present.
GLint samples = GetSampleCount(displayVk, mState.config);
ANGLE_VK_CHECK(displayVk, samples > 0, VK_ERROR_INITIALIZATION_FAILED);
if (samples > 1)
{
const VkImageUsageFlags usage = kSurfaceVKColorImageUsageFlags;
ANGLE_TRY(mColorImageMS.init(displayVk, gl::TextureType::_2D, extents, format, samples,
usage, 1, 1));
ANGLE_TRY(mColorImageMS.initMemory(displayVk, renderer->getMemoryProperties(),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
ANGLE_TRY(mColorImageMS.initImageView(displayVk, gl::TextureType::_2D,
VK_IMAGE_ASPECT_COLOR_BIT, gl::SwizzleState(),
&mColorImageViewMS, 0, 1));
// Clear the image if it has emulated channels.
ANGLE_TRY(
mColorImageMS.clearIfEmulatedFormat(displayVk, gl::ImageIndex::Make2D(0), format));
}
mSwapchainImages.resize(imageCount);
for (uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex)
......@@ -576,12 +615,21 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
SwapchainImage &member = mSwapchainImages[imageIndex];
member.image.init2DWeakReference(swapchainImages[imageIndex], extents, format, 1);
ANGLE_TRY(member.image.initImageView(displayVk, gl::TextureType::_2D,
VK_IMAGE_ASPECT_COLOR_BIT, gl::SwizzleState(),
&member.imageView, 0, 1));
// Clear the image if it has emulated channels.
ANGLE_TRY(member.image.clearIfEmulatedFormat(displayVk, gl::ImageIndex::Make2D(0), format));
if (!mColorImageMS.valid())
{
// If the multisampled image is used, we don't need a view on the swapchain image, as
// it's only used as a resolve destination. This has the added benefit that we can't
// accidentally use this image.
ANGLE_TRY(member.image.initImageView(displayVk, gl::TextureType::_2D,
VK_IMAGE_ASPECT_COLOR_BIT, gl::SwizzleState(),
&member.imageView, 0, 1));
// Clear the image if it has emulated channels. If a multisampled image exists, this
// image will be unused until a pre-present resolve, at which point it will be fully
// initialized and wouldn't need a clear.
ANGLE_TRY(
member.image.clearIfEmulatedFormat(displayVk, gl::ImageIndex::Make2D(0), format));
}
}
// Initialize depth/stencil if requested.
......@@ -591,8 +639,8 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
const VkImageUsageFlags dsUsage = kSurfaceVKDepthStencilImageUsageFlags;
ANGLE_TRY(mDepthStencilImage.init(displayVk, gl::TextureType::_2D, extents, dsFormat, 1,
dsUsage, 1, 1));
ANGLE_TRY(mDepthStencilImage.init(displayVk, gl::TextureType::_2D, extents, dsFormat,
samples, dsUsage, 1, 1));
ANGLE_TRY(mDepthStencilImage.initMemory(displayVk, renderer->getMemoryProperties(),
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT));
......@@ -611,6 +659,11 @@ angle::Result WindowSurfaceVk::recreateSwapchain(DisplayVk *displayVk,
return angle::Result::Continue;
}
bool WindowSurfaceVk::isMultiSampled() const
{
return mColorImageMS.valid();
}
angle::Result WindowSurfaceVk::checkForOutOfDateSwapchain(DisplayVk *displayVk,
uint32_t swapHistoryIndex,
bool presentOutOfDate)
......@@ -666,6 +719,16 @@ void WindowSurfaceVk::releaseSwapchainImages(RendererVk *renderer)
}
}
if (mColorImageMS.valid())
{
Serial serial = mColorImageMS.getStoredQueueSerial();
mColorImageMS.releaseImage(renderer);
mColorImageMS.releaseStagingBuffer(renderer);
renderer->releaseObject(serial, &mColorImageViewMS);
renderer->releaseObject(serial, &mFramebufferMS);
}
for (SwapchainImage &swapchainImage : mSwapchainImages)
{
Serial imageSerial = swapchainImage.image.getStoredQueueSerial();
......@@ -729,7 +792,35 @@ angle::Result WindowSurfaceVk::present(DisplayVk *displayVk,
SwapchainImage &image = mSwapchainImages[mCurrentSwapchainImageIndex];
vk::CommandBuffer *swapCommands = nullptr;
ANGLE_TRY(image.image.recordCommands(displayVk, &swapCommands));
if (mColorImageMS.valid())
{
// Transition the multisampled image to TRANSFER_SRC for resolve.
vk::CommandBuffer *multisampledTransition = nullptr;
ANGLE_TRY(mColorImageMS.recordCommands(displayVk, &multisampledTransition));
mColorImageMS.changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::TransferSrc,
multisampledTransition);
// Setup graph dependency between the swapchain image and the multisampled one.
image.image.addReadDependency(&mColorImageMS);
VkImageResolve resolveRegion = {};
resolveRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
resolveRegion.srcSubresource.mipLevel = 0;
resolveRegion.srcSubresource.baseArrayLayer = 0;
resolveRegion.srcSubresource.layerCount = 1;
resolveRegion.srcOffset = {};
resolveRegion.dstSubresource = resolveRegion.srcSubresource;
resolveRegion.dstOffset = {};
gl_vk::GetExtent(image.image.getExtents(), &resolveRegion.extent);
ANGLE_TRY(image.image.recordCommands(displayVk, &swapCommands));
mColorImageMS.resolve(&image.image, resolveRegion, swapCommands);
}
else
{
ANGLE_TRY(image.image.recordCommands(displayVk, &swapCommands));
}
image.image.changeLayout(VK_IMAGE_ASPECT_COLOR_BIT, vk::ImageLayout::Present, swapCommands);
......@@ -860,8 +951,13 @@ angle::Result WindowSurfaceVk::nextSwapchainImage(DisplayVk *displayVk)
// the next context that writes to the image.
image.image.resetQueueSerial();
// Update RenderTarget pointers.
mColorRenderTarget.updateSwapchainImage(&image.image, &image.imageView);
// Update RenderTarget pointers to this swapchain image if not multisampling. Note: a possible
// optimization is to defer the |vkAcquireNextImageKHR| call itself to |present()| if
// multisampling, as the swapchain image is essentially unused until then.
if (!mColorImageMS.valid())
{
mColorRenderTarget.updateSwapchainImage(&image.image, &image.imageView);
}
return angle::Result::Continue;
}
......@@ -979,7 +1075,9 @@ angle::Result WindowSurfaceVk::getCurrentFramebuffer(vk::Context *context,
const vk::RenderPass &compatibleRenderPass,
vk::Framebuffer **framebufferOut)
{
vk::Framebuffer &currentFramebuffer = mSwapchainImages[mCurrentSwapchainImageIndex].framebuffer;
vk::Framebuffer &currentFramebuffer =
isMultiSampled() ? mFramebufferMS
: mSwapchainImages[mCurrentSwapchainImageIndex].framebuffer;
if (currentFramebuffer.valid())
{
......@@ -1002,11 +1100,20 @@ angle::Result WindowSurfaceVk::getCurrentFramebuffer(vk::Context *context,
framebufferInfo.height = static_cast<uint32_t>(extents.height);
framebufferInfo.layers = 1;
for (SwapchainImage &swapchainImage : mSwapchainImages)
if (isMultiSampled())
{
imageViews[0] = swapchainImage.imageView.getHandle();
ANGLE_VK_TRY(context,
swapchainImage.framebuffer.init(context->getDevice(), framebufferInfo));
// If multisampled, there is only a single color image and framebuffer.
imageViews[0] = mColorImageViewMS.getHandle();
ANGLE_VK_TRY(context, mFramebufferMS.init(context->getDevice(), framebufferInfo));
}
else
{
for (SwapchainImage &swapchainImage : mSwapchainImages)
{
imageViews[0] = swapchainImage.imageView.getHandle();
ANGLE_VK_TRY(context,
swapchainImage.framebuffer.init(context->getDevice(), framebufferInfo));
}
}
ASSERT(currentFramebuffer.valid());
......@@ -1041,7 +1148,8 @@ angle::Result WindowSurfaceVk::initializeContents(const gl::Context *context,
ASSERT(mSwapchainImages.size() > 0);
ASSERT(mCurrentSwapchainImageIndex < mSwapchainImages.size());
vk::ImageHelper *image = &mSwapchainImages[mCurrentSwapchainImageIndex].image;
vk::ImageHelper *image =
isMultiSampled() ? &mColorImageMS : &mSwapchainImages[mCurrentSwapchainImageIndex].image;
image->stageSubresourceRobustClear(imageIndex, image->getFormat().angleFormat());
ANGLE_TRY(image->flushAllStagedUpdates(contextVk));
......
......@@ -71,7 +71,8 @@ class OffscreenSurfaceVk : public SurfaceImpl
angle::Result initialize(DisplayVk *displayVk,
EGLint width,
EGLint height,
const vk::Format &vkFormat);
const vk::Format &vkFormat,
GLint samples);
void destroy(const egl::Display *display);
vk::ImageHelper image;
......@@ -164,6 +165,8 @@ class WindowSurfaceVk : public SurfaceImpl
bool &swapchainOutOfDate);
angle::Result swapImpl(DisplayVk *displayVk, EGLint *rects, EGLint n_rects);
bool isMultiSampled() const;
VkSurfaceCapabilitiesKHR mSurfaceCaps;
std::vector<VkPresentModeKHR> mPresentModes;
......@@ -235,8 +238,14 @@ class WindowSurfaceVk : public SurfaceImpl
std::array<SwapHistory, kSwapHistorySize> mSwapHistory;
size_t mCurrentSwapHistoryIndex;
// Depth/stencil image. Possibly multisampled.
vk::ImageHelper mDepthStencilImage;
vk::ImageView mDepthStencilImageView;
// Multisample color image, view and framebuffer, if multisampling enabled.
vk::ImageHelper mColorImageMS;
vk::ImageView mColorImageViewMS;
vk::Framebuffer mFramebufferMS;
};
} // namespace rx
......
......@@ -52,9 +52,7 @@ SurfaceImpl *DisplayVkAndroid::createWindowSurfaceVk(const egl::SurfaceState &st
egl::ConfigSet DisplayVkAndroid::generateConfigs()
{
constexpr GLenum kColorFormats[] = {GL_RGBA8, GL_RGB8, GL_RGB565, GL_RGB10_A2, GL_RGBA16F};
constexpr EGLint kSampleCounts[] = {0};
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, kSampleCounts,
this);
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, this);
}
bool DisplayVkAndroid::checkConfigSupport(egl::Config *config)
......
......@@ -34,9 +34,7 @@ SurfaceImpl *DisplayVkFuchsia::createWindowSurfaceVk(const egl::SurfaceState &st
egl::ConfigSet DisplayVkFuchsia::generateConfigs()
{
constexpr GLenum kColorFormats[] = {GL_BGRA8_EXT, GL_BGRX8_ANGLEX};
constexpr EGLint kSampleCounts[] = {0};
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, kSampleCounts,
this);
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, this);
}
bool DisplayVkFuchsia::checkConfigSupport(egl::Config *config)
......
......@@ -496,7 +496,7 @@ void GraphicsPipelineDesc::initDefaults()
mRasterizationAndMultisampleStateInfo.minSampleShading = 0.0f;
for (uint32_t &sampleMask : mRasterizationAndMultisampleStateInfo.sampleMask)
{
sampleMask = 0;
sampleMask = 0xFFFFFFFF;
}
mRasterizationAndMultisampleStateInfo.bits.alphaToCoverageEnable = 0;
mRasterizationAndMultisampleStateInfo.bits.alphaToOneEnable = 0;
......@@ -850,6 +850,40 @@ void GraphicsPipelineDesc::updateLineWidth(GraphicsPipelineTransitionBits *trans
transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, lineWidth));
}
void GraphicsPipelineDesc::updateRasterizationSamples(GraphicsPipelineTransitionBits *transition,
uint32_t rasterizationSamples)
{
mRasterizationAndMultisampleStateInfo.bits.rasterizationSamples = rasterizationSamples;
transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
}
void GraphicsPipelineDesc::updateAlphaToCoverageEnable(GraphicsPipelineTransitionBits *transition,
bool enable)
{
mRasterizationAndMultisampleStateInfo.bits.alphaToCoverageEnable = enable;
transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
}
void GraphicsPipelineDesc::updateAlphaToOneEnable(GraphicsPipelineTransitionBits *transition,
bool enable)
{
mRasterizationAndMultisampleStateInfo.bits.alphaToOneEnable = enable;
transition->set(ANGLE_GET_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo, bits));
}
void GraphicsPipelineDesc::updateSampleMask(GraphicsPipelineTransitionBits *transition,
uint32_t maskNumber,
uint32_t mask)
{
ASSERT(maskNumber < gl::MAX_SAMPLE_MASK_WORDS);
mRasterizationAndMultisampleStateInfo.sampleMask[maskNumber] = mask;
constexpr size_t kMaskBits =
sizeof(mRasterizationAndMultisampleStateInfo.sampleMask[0]) * kBitsPerByte;
transition->set(ANGLE_GET_INDEXED_TRANSITION_BIT(mRasterizationAndMultisampleStateInfo,
sampleMask, maskNumber, kMaskBits));
}
void GraphicsPipelineDesc::updateBlendColor(GraphicsPipelineTransitionBits *transition,
const gl::ColorF &color)
{
......
......@@ -195,11 +195,11 @@ struct RasterizationStateBits final
uint32_t polygonMode : 4;
uint32_t cullMode : 4;
uint32_t frontFace : 4;
uint32_t depthBiasEnable : 4;
uint32_t rasterizationSamples : 4;
uint32_t depthBiasEnable : 1;
uint32_t sampleShadingEnable : 1;
uint32_t alphaToCoverageEnable : 1;
uint32_t alphaToOneEnable : 2;
uint32_t alphaToOneEnable : 1;
uint32_t rasterizationSamples : 8;
};
constexpr size_t kRasterizationStateBitsSize = sizeof(RasterizationStateBits);
......@@ -384,6 +384,15 @@ class GraphicsPipelineDesc final
bool invertFrontFace);
void updateLineWidth(GraphicsPipelineTransitionBits *transition, float lineWidth);
// Multisample states
void updateRasterizationSamples(GraphicsPipelineTransitionBits *transition,
uint32_t rasterizationSamples);
void updateAlphaToCoverageEnable(GraphicsPipelineTransitionBits *transition, bool enable);
void updateAlphaToOneEnable(GraphicsPipelineTransitionBits *transition, bool enable);
void updateSampleMask(GraphicsPipelineTransitionBits *transition,
uint32_t maskNumber,
uint32_t mask);
// RenderPass description.
const RenderPassDesc &getRenderPassDesc() const { return mRenderPassDesc; }
......
......@@ -323,13 +323,31 @@ egl::ConfigSet GenerateConfigs(const GLenum *colorFormats,
size_t colorFormatsCount,
const GLenum *depthStencilFormats,
size_t depthStencilFormatCount,
const EGLint *sampleCounts,
size_t sampleCountsCount,
DisplayVk *display)
{
ASSERT(colorFormatsCount > 0);
ASSERT(display != nullptr);
gl::SupportedSampleSet colorSampleCounts;
gl::SupportedSampleSet depthStencilSampleCounts;
gl::SupportedSampleSet sampleCounts;
const VkPhysicalDeviceLimits &limits =
display->getRenderer()->getPhysicalDeviceProperties().limits;
const uint32_t depthStencilSampleCountsLimit =
limits.framebufferDepthSampleCounts & limits.framebufferStencilSampleCounts;
vk_gl::AddSampleCounts(limits.framebufferColorSampleCounts, &colorSampleCounts);
vk_gl::AddSampleCounts(depthStencilSampleCountsLimit, &depthStencilSampleCounts);
// Always support 0 samples
colorSampleCounts.insert(0);
depthStencilSampleCounts.insert(0);
std::set_intersection(colorSampleCounts.begin(), colorSampleCounts.end(),
depthStencilSampleCounts.begin(), depthStencilSampleCounts.end(),
std::inserter(sampleCounts, sampleCounts.begin()));
egl::ConfigSet configSet;
for (size_t colorFormatIdx = 0; colorFormatIdx < colorFormatsCount; colorFormatIdx++)
......@@ -346,12 +364,22 @@ egl::ConfigSet GenerateConfigs(const GLenum *colorFormats,
ASSERT(depthStencilFormats[depthStencilFormatIdx] == GL_NONE ||
depthStencilFormatInfo.sized);
for (size_t sampleCountIndex = 0; sampleCountIndex < sampleCountsCount;
sampleCountIndex++)
const gl::SupportedSampleSet *configSampleCounts = &sampleCounts;
// If there is no depth/stencil buffer, use the color samples set.
if (depthStencilFormats[depthStencilFormatIdx] == GL_NONE)
{
configSampleCounts = &colorSampleCounts;
}
// If there is no color buffer, use the depth/stencil samples set.
else if (colorFormats[colorFormatIdx] == GL_NONE)
{
configSampleCounts = &depthStencilSampleCounts;
}
for (EGLint sampleCount : *configSampleCounts)
{
egl::Config config =
GenerateDefaultConfig(display->getRenderer(), colorFormatInfo,
depthStencilFormatInfo, sampleCounts[sampleCountIndex]);
egl::Config config = GenerateDefaultConfig(display->getRenderer(), colorFormatInfo,
depthStencilFormatInfo, sampleCount);
if (display->checkConfigSupport(&config))
{
configSet.add(config);
......
......@@ -41,18 +41,15 @@ egl::ConfigSet GenerateConfigs(const GLenum *colorFormats,
size_t colorFormatsCount,
const GLenum *depthStencilFormats,
size_t depthStencilFormatCount,
const EGLint *sampleCounts,
size_t sampleCountsCount,
DisplayVk *display);
template <size_t ColorFormatCount, size_t DepthStencilFormatCount, size_t SampleCountsCount>
template <size_t ColorFormatCount, size_t DepthStencilFormatCount>
egl::ConfigSet GenerateConfigs(const GLenum (&colorFormats)[ColorFormatCount],
const GLenum (&depthStencilFormats)[DepthStencilFormatCount],
const EGLint (&sampleCounts)[SampleCountsCount],
DisplayVk *display)
{
return GenerateConfigs(colorFormats, ColorFormatCount, depthStencilFormats,
DepthStencilFormatCount, sampleCounts, SampleCountsCount, display);
DepthStencilFormatCount, display);
}
} // namespace egl_vk
......
......@@ -18,19 +18,6 @@ namespace rx
{
namespace
{
void AddSampleCounts(VkSampleCountFlags sampleCounts, gl::SupportedSampleSet *outSet)
{
// The possible bits are VK_SAMPLE_COUNT_n_BIT = n, with n = 1 << b. At the time of this
// writing, b is in [0, 6], however, we test all 32 bits in case the enum is extended.
for (unsigned int i = 0; i < 32; ++i)
{
if ((sampleCounts & (1 << i)) != 0)
{
outSet->insert(1 << i);
}
}
}
void FillTextureFormatCaps(RendererVk *renderer, VkFormat format, gl::TextureCaps *outTextureCaps)
{
const VkPhysicalDeviceLimits &physicalDeviceLimits =
......@@ -52,15 +39,15 @@ void FillTextureFormatCaps(RendererVk *renderer, VkFormat format, gl::TextureCap
{
if (hasColorAttachmentFeatureBit)
{
AddSampleCounts(physicalDeviceLimits.framebufferColorSampleCounts,
&outTextureCaps->sampleCounts);
vk_gl::AddSampleCounts(physicalDeviceLimits.framebufferColorSampleCounts,
&outTextureCaps->sampleCounts);
}
if (hasDepthAttachmentFeatureBit)
{
AddSampleCounts(physicalDeviceLimits.framebufferDepthSampleCounts,
&outTextureCaps->sampleCounts);
AddSampleCounts(physicalDeviceLimits.framebufferStencilSampleCounts,
&outTextureCaps->sampleCounts);
vk_gl::AddSampleCounts(physicalDeviceLimits.framebufferDepthSampleCounts,
&outTextureCaps->sampleCounts);
vk_gl::AddSampleCounts(physicalDeviceLimits.framebufferStencilSampleCounts,
&outTextureCaps->sampleCounts);
}
}
}
......
......@@ -1842,6 +1842,18 @@ angle::Result ImageHelper::generateMipmapsWithBlit(ContextVk *contextVk, GLuint
return angle::Result::Continue;
}
void ImageHelper::resolve(ImageHelper *dest,
const VkImageResolve &region,
vk::CommandBuffer *commandBuffer)
{
ASSERT(mCurrentLayout == vk::ImageLayout::TransferSrc);
dest->changeLayout(region.dstSubresource.aspectMask, vk::ImageLayout::TransferDst,
commandBuffer);
commandBuffer->resolveImage(getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dest->getImage(),
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
void ImageHelper::removeStagedUpdates(RendererVk *renderer, const gl::ImageIndex &index)
{
// Find any staged updates for this index and removes them from the pending list.
......
......@@ -582,6 +582,7 @@ class ImageHelper final : public CommandGraphResource
// Create a 2D[Array] for staging purposes. Used by:
//
// - TextureVk::copySubImageImplWithDraw
// - FramebufferVk::readPixelsImpl
//
angle::Result init2DStaging(Context *context,
const MemoryProperties &memoryProperties,
......@@ -643,6 +644,10 @@ class ImageHelper final : public CommandGraphResource
angle::Result generateMipmapsWithBlit(ContextVk *contextVk, GLuint maxLevel);
// Resolve this image into a destination image. This image should be in the TransferSrc layout.
// The destination image is automatically transitioned into TransferDst.
void resolve(ImageHelper *dest, const VkImageResolve &region, vk::CommandBuffer *commandBuffer);
// Data staging
void removeStagedUpdates(RendererVk *renderer, const gl::ImageIndex &index);
......
......@@ -825,4 +825,17 @@ void GetViewport(const gl::Rectangle &viewport,
}
}
} // namespace gl_vk
namespace vk_gl
{
void AddSampleCounts(VkSampleCountFlags sampleCounts, gl::SupportedSampleSet *setOut)
{
// The possible bits are VK_SAMPLE_COUNT_n_BIT = n, with n = 1 << b. At the time of this
// writing, b is in [0, 6], however, we test all 32 bits in case the enum is extended.
for (unsigned long bit : angle::BitSet32<32>(sampleCounts))
{
setOut->insert(1 << bit);
}
}
} // namespace vk_gl
} // namespace rx
......@@ -544,6 +544,12 @@ void GetViewport(const gl::Rectangle &viewport,
VkViewport *viewportOut);
} // namespace gl_vk
namespace vk_gl
{
// Find set bits in sampleCounts and add the corresponding sample count to the set.
void AddSampleCounts(VkSampleCountFlags sampleCounts, gl::SupportedSampleSet *outSet);
} // namespace vk_gl
} // namespace rx
#define ANGLE_VK_TRY(context, command) \
......
......@@ -223,7 +223,7 @@ class CommandBuffer : public WrappedObject<CommandBuffer, VkCommandBuffer>
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
VkImageBlit *pRegions,
const VkImageBlit *regions,
VkFilter filter);
void clearColorImage(const Image &image,
......@@ -313,6 +313,12 @@ class CommandBuffer : public WrappedObject<CommandBuffer, VkCommandBuffer>
VkResult reset();
void resetEvent(VkEvent event, VkPipelineStageFlags stageMask);
void resetQueryPool(VkQueryPool queryPool, uint32_t firstQuery, uint32_t queryCount);
void resolveImage(const Image &srcImage,
VkImageLayout srcImageLayout,
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
const VkImageResolve *regions);
void waitEvents(uint32_t eventCount,
const VkEvent *events,
VkPipelineStageFlags srcStageMask,
......@@ -578,13 +584,13 @@ ANGLE_INLINE void CommandBuffer::blitImage(const Image &srcImage,
const Image &dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
VkImageBlit *pRegions,
const VkImageBlit *regions,
VkFilter filter)
{
ASSERT(valid() && srcImage.valid() && dstImage.valid());
ASSERT(regionCount == 1);
vkCmdBlitImage(mHandle, srcImage.getHandle(), srcImageLayout, dstImage.getHandle(),
dstImageLayout, 1, pRegions, filter);
dstImageLayout, 1, regions, filter);
}
ANGLE_INLINE VkResult CommandBuffer::begin(const VkCommandBufferBeginInfo &info)
......
......@@ -35,9 +35,7 @@ SurfaceImpl *DisplayVkWin32::createWindowSurfaceVk(const egl::SurfaceState &stat
egl::ConfigSet DisplayVkWin32::generateConfigs()
{
constexpr GLenum kColorFormats[] = {GL_BGRA8_EXT, GL_BGRX8_ANGLEX};
constexpr EGLint kSampleCounts[] = {0};
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, kSampleCounts,
this);
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, this);
}
bool DisplayVkWin32::checkConfigSupport(egl::Config *config)
......
......@@ -86,9 +86,7 @@ SurfaceImpl *DisplayVkXcb::createWindowSurfaceVk(const egl::SurfaceState &state,
egl::ConfigSet DisplayVkXcb::generateConfigs()
{
constexpr GLenum kColorFormats[] = {GL_BGRA8_EXT, GL_BGRX8_ANGLEX};
constexpr EGLint kSampleCounts[] = {0};
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, kSampleCounts,
this);
return egl_vk::GenerateConfigs(kColorFormats, egl_vk::kConfigDepthStencilFormats, this);
}
bool DisplayVkXcb::checkConfigSupport(egl::Config *config)
......
......@@ -80,6 +80,7 @@ angle_end2end_tests_sources = [
"gl_tests/MipmapTest.cpp",
"gl_tests/MultiDrawTest.cpp",
"gl_tests/MultisampleCompatibilityTest.cpp",
"gl_tests/MultisampleTest.cpp",
"gl_tests/MultithreadingTest.cpp",
"gl_tests/MultiviewDrawTest.cpp",
"gl_tests/media/pixel.inl",
......
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MultisampleTest: Tests of multisampled default framebuffer
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/OSWindow.h"
#include "util/shader_utils.h"
using namespace angle;
namespace
{
class MultisampleTest : public ANGLETest
{
protected:
void testSetUp() override
{
// Get display.
EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
mDisplay = eglGetPlatformDisplayEXT(
EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
ASSERT_TRUE(mDisplay != EGL_NO_DISPLAY);
ASSERT_TRUE(eglInitialize(mDisplay, nullptr, nullptr) == EGL_TRUE);
// Nexus 5X fails to eglMakeCurrent with a config it advertises it supports.
// http://anglebug.com/3464
ANGLE_SKIP_TEST_IF(IsNexus5X());
// Find a config that uses RGBA8 and allows 4x multisampling.
const EGLint configAttributes[] = {
EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8,
EGL_SAMPLE_BUFFERS, 1, EGL_SAMPLES, 4, EGL_NONE};
EGLint configCount;
EGLConfig multisampledConfig;
EGLint ret =
eglChooseConfig(mDisplay, configAttributes, &multisampledConfig, 1, &configCount);
mMultisampledConfigExists = ret && configCount > 0;
if (!mMultisampledConfigExists)
{
return;
}
// Create a window, context and surface if multisampling is possible.
mOSWindow = OSWindow::New();
mOSWindow->initialize("MultisampleTest", kWindowSize, kWindowSize);
mOSWindow->setVisible(true);
EGLint contextAttributes[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR,
GetParam().majorVersion,
EGL_CONTEXT_MINOR_VERSION_KHR,
GetParam().minorVersion,
EGL_NONE,
};
mContext =
eglCreateContext(mDisplay, multisampledConfig, EGL_NO_CONTEXT, contextAttributes);
ASSERT_TRUE(mContext != EGL_NO_CONTEXT);
mSurface = eglCreateWindowSurface(mDisplay, multisampledConfig,
mOSWindow->getNativeWindow(), nullptr);
ASSERT_EGL_SUCCESS();
eglMakeCurrent(mDisplay, mSurface, mSurface, mContext);
ASSERT_EGL_SUCCESS();
}
void testTearDown() override
{
if (mSurface)
{
eglSwapBuffers(mDisplay, mSurface);
}
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (mSurface)
{
eglDestroySurface(mDisplay, mSurface);
ASSERT_EGL_SUCCESS();
}
if (mContext != EGL_NO_CONTEXT)
{
eglDestroyContext(mDisplay, mContext);
ASSERT_EGL_SUCCESS();
}
if (mOSWindow)
{
OSWindow::Delete(&mOSWindow);
}
eglTerminate(mDisplay);
}
void prepareVertexBuffer(GLBuffer &vertexBuffer,
const Vector3 *vertices,
size_t vertexCount,
GLint positionLocation)
{
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(*vertices) * vertexCount, vertices, GL_STATIC_DRAW);
glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLocation);
}
protected:
static constexpr int kWindowSize = 8;
OSWindow *mOSWindow = nullptr;
EGLDisplay mDisplay = EGL_NO_DISPLAY;
EGLContext mContext = EGL_NO_CONTEXT;
EGLSurface mSurface = EGL_NO_SURFACE;
bool mMultisampledConfigExists = false;
};
// Test point rendering on a multisampled surface. GLES2 section 3.3.1.
TEST_P(MultisampleTest, Point)
{
ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
constexpr char kPointsVS[] = R"(precision highp float;
attribute vec4 a_position;
void main()
{
gl_PointSize = 3.0;
gl_Position = a_position;
})";
ANGLE_GL_PROGRAM(program, kPointsVS, essl1_shaders::fs::Red());
glUseProgram(program);
const GLint positionLocation = glGetAttribLocation(program, "a_position");
GLBuffer vertexBuffer;
const Vector3 vertices[1] = {{0.0f, 0.0f, 0.0f}};
prepareVertexBuffer(vertexBuffer, vertices, 1, positionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_POINTS, 0, 1);
ASSERT_GL_NO_ERROR();
// The center pixels should be all red.
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2 - 1, kWindowSize / 2, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2, kWindowSize / 2 - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 2 - 1, kWindowSize / 2 - 1, GLColor::red);
// Border pixels should be between red and black, and not exactly either; corners are darker and
// sides are brighter.
const GLColor kSideColor = {128, 0, 0, 128};
const GLColor kCornerColor = {64, 0, 0, 64};
constexpr int kErrorMargin = 16;
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 2, kWindowSize / 2 - 2, kCornerColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 2, kWindowSize / 2 + 1, kCornerColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 + 1, kWindowSize / 2 - 2, kCornerColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 + 1, kWindowSize / 2 + 1, kCornerColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 2, kWindowSize / 2 - 1, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 2, kWindowSize / 2, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 1, kWindowSize / 2 - 2, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 - 1, kWindowSize / 2 + 1, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2, kWindowSize / 2 - 2, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2, kWindowSize / 2 + 1, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 + 1, kWindowSize / 2 - 1, kSideColor, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(kWindowSize / 2 + 1, kWindowSize / 2, kSideColor, kErrorMargin);
}
// Test line rendering on a multisampled surface. GLES2 section 3.4.4.
TEST_P(MultisampleTest, Line)
{
ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program);
const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
GLBuffer vertexBuffer;
const Vector3 vertices[2] = {{-1.0f, -0.3f, 0.0f}, {1.0f, 0.3f, 0.0f}};
prepareVertexBuffer(vertexBuffer, vertices, 2, positionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_LINES, 0, 2);
ASSERT_GL_NO_ERROR();
// The line goes from left to right at about -17 degrees slope. It renders as such (captured
// with renderdoc):
//
// D D = Dark Red (0.25) or (0.5)
// BRA R = Red (1.0)
// ARB M = Middle Red (0.75)
// D B = Bright Red (1.0 or 0.75)
// A = Any red (0.5, 0.75 or 1.0)
//
// Verify that rendering is done as above.
const GLColor kDarkRed = {128, 0, 0, 128};
const GLColor kMidRed = {192, 0, 0, 192};
constexpr int kErrorMargin = 16;
constexpr int kLargeMargin = 80;
static_assert(kWindowSize == 8, "Verification code written for 8x8 window");
EXPECT_PIXEL_COLOR_NEAR(0, 2, kDarkRed, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(1, 3, GLColor::red, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(2, 3, GLColor::red, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(3, 3, kMidRed, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(4, 4, kMidRed, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(5, 4, GLColor::red, kErrorMargin);
EXPECT_PIXEL_COLOR_NEAR(6, 4, GLColor::red, kLargeMargin);
EXPECT_PIXEL_COLOR_NEAR(7, 5, kDarkRed, kLargeMargin);
}
// Test polygon rendering on a multisampled surface. GLES2 section 3.5.3.
TEST_P(MultisampleTest, Triangle)
{
ANGLE_SKIP_TEST_IF(!mMultisampledConfigExists);
ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
glUseProgram(program);
const GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
GLBuffer vertexBuffer;
const Vector3 vertices[3] = {{-1.0f, -1.0f, 0.0f}, {-1.0f, 1.0f, 0.0f}, {1.0f, -1.0f, 0.0f}};
prepareVertexBuffer(vertexBuffer, vertices, 3, positionLocation);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
ASSERT_GL_NO_ERROR();
// Top-left pixels should be all red.
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(kWindowSize / 4, kWindowSize / 4, GLColor::red);
// Diagonal pixels from bottom-left to top-right are between red and black. Pixels above the
// diagonal are red and pixels below it are black.
const GLColor kMidRed = {128, 0, 0, 128};
constexpr int kErrorMargin = 16;
for (int i = 1; i + 1 < kWindowSize; ++i)
{
int j = kWindowSize - 1 - i;
EXPECT_PIXEL_COLOR_NEAR(i, j, kMidRed, kErrorMargin);
EXPECT_PIXEL_COLOR_EQ(i, j - 1, GLColor::red);
EXPECT_PIXEL_COLOR_EQ(i, j + 1, GLColor::transparentBlack);
}
}
ANGLE_INSTANTIATE_TEST(MultisampleTest,
WithNoFixture(ES2_D3D11()),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES31_D3D11()),
WithNoFixture(ES2_OPENGL()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES31_OPENGL()),
WithNoFixture(ES2_OPENGLES()),
WithNoFixture(ES3_OPENGLES()),
WithNoFixture(ES31_OPENGLES()),
WithNoFixture(ES2_VULKAN()),
WithNoFixture(ES3_VULKAN()));
} // anonymous 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