GL_EXT_multisampled_render_to_texture extension. Part 2.
For textures that use this extension, a multisampled texture is
implicitly created for the texture.
Upon write or read, the multisampled texture is either return
to be drawn to or resolved and returned as a single sampled texture.
This is the functionality change with end2end tests.
Bug: angleproject:980428
Change-Id: I5776875a132fed7a3f4f00fb02f9e8e250684630
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1773717
Commit-Queue: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 8bce1dd..dde2219 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -4144,9 +4144,10 @@
if (renderbuffer.value != 0)
{
Renderbuffer *renderbufferObject = getRenderbuffer(renderbuffer);
+ GLsizei rbSamples = renderbufferObject->getSamples();
- framebuffer->setAttachment(this, GL_RENDERBUFFER, attachment, gl::ImageIndex(),
- renderbufferObject);
+ framebuffer->setAttachmentMultisample(this, GL_RENDERBUFFER, attachment, gl::ImageIndex(),
+ renderbufferObject, rbSamples);
}
else
{
@@ -5632,7 +5633,26 @@
GLuint texture,
GLint level,
GLsizei samples)
-{}
+{
+ Framebuffer *framebuffer = mState.getTargetFramebuffer(target);
+ ASSERT(framebuffer);
+
+ if (texture != 0)
+ {
+ TextureTarget textargetPacked = FromGLenum<TextureTarget>(textarget);
+ TextureID texturePacked = FromGL<TextureID>(texture);
+ Texture *textureObj = getTexture(texturePacked);
+ ImageIndex index = ImageIndex::MakeFromTarget(textargetPacked, level, 1);
+ framebuffer->setAttachmentMultisample(this, GL_TEXTURE, attachment, index, textureObj,
+ samples);
+ }
+ else
+ {
+ framebuffer->resetAttachment(this, attachment);
+ }
+
+ mState.setObjectDirty(target);
+}
void Context::getSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values)
{
@@ -8523,7 +8543,8 @@
{
static_assert(GL_MAX_SAMPLES_ANGLE == GL_MAX_SAMPLES,
"GL_MAX_SAMPLES_ANGLE not equal to GL_MAX_SAMPLES");
- if ((getClientMajorVersion() < 3) && !getExtensions().framebufferMultisample)
+ if ((getClientMajorVersion() < 3) && !(getExtensions().framebufferMultisample ||
+ getExtensions().multisampledRenderToTexture))
{
return false;
}
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index 7fa5a3a..e6881eb 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -140,11 +140,43 @@
return true;
}
+bool CheckAttachmentSampleCounts(const Context *context,
+ GLsizei currAttachmentSamples,
+ GLsizei samples,
+ bool colorAttachment)
+{
+ if (currAttachmentSamples != samples)
+ {
+ if (colorAttachment)
+ {
+ // APPLE_framebuffer_multisample, which EXT_draw_buffers refers to, requires that
+ // all color attachments have the same number of samples for the FBO to be complete.
+ return false;
+ }
+ else
+ {
+ // CHROMIUM_framebuffer_mixed_samples allows a framebuffer to be considered complete
+ // when its depth or stencil samples are a multiple of the number of color samples.
+ if (!context->getExtensions().framebufferMixedSamples)
+ {
+ return false;
+ }
+
+ if ((currAttachmentSamples % std::max(samples, 1)) != 0)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
bool CheckAttachmentSampleCompleteness(const Context *context,
const FramebufferAttachment &attachment,
bool colorAttachment,
Optional<int> *samples,
- Optional<bool> *fixedSampleLocations)
+ Optional<bool> *fixedSampleLocations,
+ Optional<int> *renderToTextureSamples)
{
ASSERT(attachment.isAttached());
@@ -152,6 +184,12 @@
{
const Texture *texture = attachment.getTexture();
ASSERT(texture);
+ GLenum internalFormat = attachment.getFormat().info->internalFormat;
+ const TextureCaps &formatCaps = context->getTextureCaps().get(internalFormat);
+ if (static_cast<GLuint>(attachment.getSamples()) > formatCaps.getMaxSamples())
+ {
+ return false;
+ }
const ImageIndex &attachmentImageIndex = attachment.getTextureImageIndex();
bool fixedSampleloc = texture->getAttachmentFixedSampleLocations(attachmentImageIndex);
@@ -165,29 +203,34 @@
}
}
- if (samples->valid())
+ if (renderToTextureSamples->valid())
{
- if (attachment.getSamples() != samples->value())
+ // Only check against RenderToTextureSamples if they actually exist.
+ if (renderToTextureSamples->value() !=
+ FramebufferAttachment::kDefaultRenderToTextureSamples)
{
- if (colorAttachment)
+ if (!CheckAttachmentSampleCounts(context, attachment.getRenderToTextureSamples(),
+ renderToTextureSamples->value(), colorAttachment))
{
- // APPLE_framebuffer_multisample, which EXT_draw_buffers refers to, requires that
- // all color attachments have the same number of samples for the FBO to be complete.
return false;
}
- else
- {
- // CHROMIUM_framebuffer_mixed_samples allows a framebuffer to be considered complete
- // when its depth or stencil samples are a multiple of the number of color samples.
- if (!context->getExtensions().framebufferMixedSamples)
- {
- return false;
- }
+ }
+ }
+ else
+ {
+ *renderToTextureSamples = attachment.getRenderToTextureSamples();
+ }
- if ((attachment.getSamples() % std::max(samples->value(), 1)) != 0)
- {
- return false;
- }
+ if (samples->valid())
+ {
+ // RenderToTextureSamples takes precedence if they exist.
+ if (renderToTextureSamples->value() ==
+ FramebufferAttachment::kDefaultRenderToTextureSamples)
+ {
+ if (!CheckAttachmentSampleCounts(context, attachment.getSamples(), samples->value(),
+ colorAttachment))
+ {
+ return false;
}
}
}
@@ -1049,6 +1092,7 @@
Optional<int> samples;
Optional<bool> fixedSampleLocations;
bool hasRenderbuffer = false;
+ Optional<int> renderToTextureSamples;
const FramebufferAttachment *firstAttachment = getFirstNonNullAttachment();
@@ -1071,7 +1115,7 @@
}
if (!CheckAttachmentSampleCompleteness(context, colorAttachment, true, &samples,
- &fixedSampleLocations))
+ &fixedSampleLocations, &renderToTextureSamples))
{
return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
}
@@ -1148,7 +1192,7 @@
}
if (!CheckAttachmentSampleCompleteness(context, depthAttachment, false, &samples,
- &fixedSampleLocations))
+ &fixedSampleLocations, &renderToTextureSamples))
{
return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
}
@@ -1193,7 +1237,7 @@
}
if (!CheckAttachmentSampleCompleteness(context, stencilAttachment, false, &samples,
- &fixedSampleLocations))
+ &fixedSampleLocations, &renderToTextureSamples))
{
return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
}
@@ -1596,10 +1640,15 @@
int Framebuffer::getSamples(const Context *context)
{
- return (isComplete(context) ? getCachedSamples(context) : 0);
+ return (isComplete(context) ? getCachedSamples(context, AttachmentSampleType::Emulated) : 0);
}
-int Framebuffer::getCachedSamples(const Context *context) const
+int Framebuffer::getResourceSamples(const Context *context)
+{
+ return (isComplete(context) ? getCachedSamples(context, AttachmentSampleType::Resource) : 0);
+}
+
+int Framebuffer::getCachedSamples(const Context *context, AttachmentSampleType sampleType) const
{
ASSERT(mCachedStatus.valid() && mCachedStatus.value() == GL_FRAMEBUFFER_COMPLETE);
@@ -1609,7 +1658,15 @@
if (firstNonNullAttachment)
{
ASSERT(firstNonNullAttachment->isAttached());
- return firstNonNullAttachment->getSamples();
+ if (sampleType == AttachmentSampleType::Resource)
+ {
+ return firstNonNullAttachment->getResourceSamples();
+ }
+ else
+ {
+ ASSERT(sampleType == AttachmentSampleType::Emulated);
+ return firstNonNullAttachment->getSamples();
+ }
}
// No attachments found.
@@ -1641,6 +1698,18 @@
FramebufferAttachment::kDefaultRenderToTextureSamples);
}
+void Framebuffer::setAttachmentMultisample(const Context *context,
+ GLenum type,
+ GLenum binding,
+ const ImageIndex &textureIndex,
+ FramebufferAttachmentObject *resource,
+ GLsizei samples)
+{
+ setAttachment(context, type, binding, textureIndex, resource,
+ FramebufferAttachment::kDefaultNumViews,
+ FramebufferAttachment::kDefaultBaseViewIndex, false, samples);
+}
+
void Framebuffer::setAttachment(const Context *context,
GLenum type,
GLenum binding,
diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h
index 73d8ec8..f373d01 100644
--- a/src/libANGLE/Framebuffer.h
+++ b/src/libANGLE/Framebuffer.h
@@ -49,6 +49,15 @@
class Texture;
class TextureCapsMap;
+enum class AttachmentSampleType
+{
+ // The sample count of the actual resource
+ Resource,
+ // If render_to_texture is used, this is the sample count of the multisampled
+ // texture that is created behind the scenes.
+ Emulated
+};
+
class FramebufferState final : angle::NonCopyable
{
public:
@@ -181,6 +190,12 @@
GLenum binding,
const ImageIndex &textureIndex,
FramebufferAttachmentObject *resource);
+ void setAttachmentMultisample(const Context *context,
+ GLenum type,
+ GLenum binding,
+ const ImageIndex &textureIndex,
+ FramebufferAttachmentObject *resource,
+ GLsizei samples);
void setAttachmentMultiview(const Context *context,
GLenum type,
GLenum binding,
@@ -232,6 +247,7 @@
// This method calls checkStatus.
int getSamples(const Context *context);
+ int getResourceSamples(const Context *context);
angle::Result getSamplePosition(const Context *context, size_t index, GLfloat *xy) const;
@@ -247,6 +263,7 @@
void setDefaultLayers(GLint defaultLayers);
void invalidateCompletenessCache();
+ ANGLE_INLINE bool cachedStatusValid() { return mCachedStatus.valid(); }
ANGLE_INLINE GLenum checkStatus(const Context *context)
{
@@ -262,7 +279,7 @@
}
// For when we don't want to check completeness in getSamples().
- int getCachedSamples(const Context *context) const;
+ int getCachedSamples(const Context *context, AttachmentSampleType sampleType) const;
// Helper for checkStatus == GL_FRAMEBUFFER_COMPLETE.
ANGLE_INLINE bool isComplete(const Context *context)
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index 23f7d68..d30611c 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -1840,14 +1840,28 @@
void Texture::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
{
- ASSERT(message == angle::SubjectMessage::SubjectChanged);
- mDirtyBits.set(DIRTY_BIT_IMPLEMENTATION);
- signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
-
- // Notify siblings that we are dirty.
- if (index == rx::kTextureImageImplObserverMessageIndex)
+ switch (message)
{
- notifySiblings(message);
+ case angle::SubjectMessage::ContentsChanged:
+ // ContentsChange is originates from TextureStorage11::resolveAndReleaseTexture
+ // which resolves the underlying multisampled texture if it exists and so
+ // Texture will signal dirty storage to invalidate its own cache and the
+ // attached framebuffer's cache.
+ signalDirtyStorage(InitState::Initialized);
+ return;
+ case angle::SubjectMessage::SubjectChanged:
+ mDirtyBits.set(DIRTY_BIT_IMPLEMENTATION);
+ signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
+
+ // Notify siblings that we are dirty.
+ if (index == rx::kTextureImageImplObserverMessageIndex)
+ {
+ notifySiblings(message);
+ }
+ return;
+ default:
+ return;
}
}
+
} // namespace gl
diff --git a/src/libANGLE/queryutils.cpp b/src/libANGLE/queryutils.cpp
index da40ad2..7d544e1 100644
--- a/src/libANGLE/queryutils.cpp
+++ b/src/libANGLE/queryutils.cpp
@@ -1113,6 +1113,17 @@
*params = attachmentObject->isLayered();
break;
+ case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT:
+ if (attachmentObject->type() == GL_TEXTURE)
+ {
+ *params = attachmentObject->getSamples();
+ }
+ else
+ {
+ *params = 0;
+ }
+ break;
+
default:
UNREACHABLE();
break;
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.cpp b/src/libANGLE/renderer/d3d/TextureD3D.cpp
index 371b051..d63f6ee 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.cpp
+++ b/src/libANGLE/renderer/d3d/TextureD3D.cpp
@@ -81,6 +81,7 @@
mDirtyImages(true),
mImmutable(false),
mTexStorage(nullptr),
+ mTexStorageObserverBinding(this, kTextureStorageObserverMessageIndex),
mBaseLevel(0)
{}
@@ -796,6 +797,11 @@
return 0;
}
+void TextureD3D::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
+{
+ onStateChange(message);
+}
+
TextureD3D_2D::TextureD3D_2D(const gl::TextureState &state, RendererD3D *renderer)
: TextureD3D(state, renderer)
{
@@ -895,6 +901,10 @@
// Attempt a fast gpu copy of the pixel data to the surface
gl::Buffer *unpackBuffer = context->getState().getTargetBuffer(gl::BufferBinding::PixelUnpack);
+ if (mTexStorage)
+ {
+ ANGLE_TRY(mTexStorage->releaseMultisampledTexStorageForLevel(index.getLevelIndex()));
+ }
if (isFastUnpackable(unpackBuffer, internalFormatInfo.sizedInternalFormat) &&
isLevelComplete(index.getLevelIndex()))
{
@@ -934,6 +944,10 @@
ASSERT(index.getTarget() == gl::TextureTarget::_2D && area.depth == 1 && area.z == 0);
GLenum mipFormat = getInternalFormat(index.getLevelIndex());
+ if (mTexStorage)
+ {
+ ANGLE_TRY(mTexStorage->releaseMultisampledTexStorageForLevel(index.getLevelIndex()));
+ }
if (isFastUnpackable(unpackBuffer, mipFormat) && isLevelComplete(index.getLevelIndex()))
{
RenderTargetD3D *renderTarget = nullptr;
@@ -1481,6 +1495,7 @@
ANGLE_TRY(releaseTexStorage(context));
mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
mDirtyImages = true;
@@ -2146,6 +2161,7 @@
ANGLE_TRY(releaseTexStorage(context));
mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
mDirtyImages = true;
return angle::Result::Continue;
@@ -2808,7 +2824,8 @@
TextureStorage *newCompleteTexStorage)
{
ANGLE_TRY(releaseTexStorage(context));
- mTexStorage = newCompleteTexStorage;
+ mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
mDirtyImages = true;
// We do not support managed 3D storage, as that is D3D9/ES2-only
@@ -3533,7 +3550,8 @@
TextureStorage *newCompleteTexStorage)
{
ANGLE_TRY(releaseTexStorage(context));
- mTexStorage = newCompleteTexStorage;
+ mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
mDirtyImages = true;
// We do not support managed 2D array storage, as managed storage is ES2/D3D9 only
@@ -4075,6 +4093,7 @@
// These textures are immutable, so this should only be ever called once.
ASSERT(!mTexStorage);
mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
return angle::Result::Continue;
}
@@ -4192,6 +4211,7 @@
// These textures are immutable, so this should only be ever called once.
ASSERT(!mTexStorage);
mTexStorage = newCompleteTexStorage;
+ mTexStorageObserverBinding.bind(mTexStorage);
return angle::Result::Continue;
}
diff --git a/src/libANGLE/renderer/d3d/TextureD3D.h b/src/libANGLE/renderer/d3d/TextureD3D.h
index 3e6613e..fd918c9 100644
--- a/src/libANGLE/renderer/d3d/TextureD3D.h
+++ b/src/libANGLE/renderer/d3d/TextureD3D.h
@@ -29,7 +29,7 @@
class RenderTargetD3D;
class TextureStorage;
-class TextureD3D : public TextureImpl
+class TextureD3D : public TextureImpl, public angle::ObserverInterface
{
public:
TextureD3D(const gl::TextureState &data, RendererD3D *renderer);
@@ -113,6 +113,9 @@
GLsizei getRenderToTextureSamples();
+ // ObserverInterface implementation.
+ void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override;
+
protected:
angle::Result setImageImpl(const gl::Context *context,
const gl::ImageIndex &index,
@@ -184,6 +187,7 @@
bool mImmutable;
TextureStorage *mTexStorage;
+ angle::ObserverBinding mTexStorageObserverBinding;
private:
virtual angle::Result initializeStorage(const gl::Context *context, bool renderTarget) = 0;
diff --git a/src/libANGLE/renderer/d3d/TextureStorage.h b/src/libANGLE/renderer/d3d/TextureStorage.h
index cc83a66..f70f0b6 100644
--- a/src/libANGLE/renderer/d3d/TextureStorage.h
+++ b/src/libANGLE/renderer/d3d/TextureStorage.h
@@ -34,11 +34,14 @@
class RenderTargetD3D;
class ImageD3D;
-class TextureStorage : angle::NonCopyable
+// Dirty bit messages from TextureStorage
+constexpr size_t kTextureStorageObserverMessageIndex = 0;
+
+class TextureStorage : public angle::Subject
{
public:
TextureStorage() {}
- virtual ~TextureStorage() {}
+ ~TextureStorage() override {}
virtual angle::Result onDestroy(const gl::Context *context);
@@ -74,6 +77,8 @@
virtual void invalidateTextures() {}
// RenderToTexture methods
+ virtual angle::Result releaseMultisampledTexStorageForLevel(size_t level);
+ virtual angle::Result resolveAndReleaseTexture(const gl::Context *context);
virtual GLsizei getRenderToTextureSamples() const;
protected:
@@ -91,6 +96,16 @@
return angle::Result::Continue;
}
+inline angle::Result TextureStorage::releaseMultisampledTexStorageForLevel(size_t level)
+{
+ return angle::Result::Continue;
+}
+
+inline angle::Result TextureStorage::resolveAndReleaseTexture(const gl::Context *context)
+{
+ return angle::Result::Continue;
+}
+
inline GLsizei TextureStorage::getRenderToTextureSamples() const
{
return 0;
diff --git a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
index f95c7ed..a4b5f54 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
@@ -2703,7 +2703,7 @@
desc.ArraySize = 1;
desc.Format = formatInfo.texFormat;
desc.SampleDesc.Count = (supportedSamples == 0) ? 1 : supportedSamples;
- desc.SampleDesc.Quality = 0;
+ desc.SampleDesc.Quality = (supportedSamples == 0) ? 0 : D3D11_STANDARD_MULTISAMPLE_PATTERN;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.CPUAccessFlags = 0;
desc.MiscFlags = 0;
@@ -3558,7 +3558,8 @@
bool partialDSBlit =
(nativeFormat.depthBits > 0 && depthBlit) != (nativeFormat.stencilBits > 0 && stencilBlit);
- if (readRenderTarget11->getFormatSet().formatID ==
+ if (drawRenderTarget->getSamples() == readRenderTarget->getSamples() &&
+ readRenderTarget11->getFormatSet().formatID ==
drawRenderTarget11->getFormatSet().formatID &&
!stretchRequired && !outOfBounds && !reversalRequired && !partialDSBlit &&
!colorMaskingNeeded && (!(depthBlit || stencilBlit) || wholeBufferCopy))
diff --git a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
index 5abeff7..ac40084 100644
--- a/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp
@@ -1531,7 +1531,7 @@
mCurDisableStencil = disableStencil;
}
- bool multiSample = (fbo->getCachedSamples(context) != 0);
+ bool multiSample = (fbo->getCachedSamples(context, gl::AttachmentSampleType::Emulated) != 0);
if (multiSample != mCurRasterState.multiSample)
{
mInternalDirtyBits.set(DIRTY_BIT_RASTERIZER_STATE);
diff --git a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
index 3cc4ab4..c2c5fb4 100644
--- a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
@@ -218,6 +218,7 @@
const gl::SamplerState &sampler,
const d3d11::SharedSRV **outSRV)
{
+ ANGLE_TRY(resolveAndReleaseTexture(context));
// Make sure to add the level offset for our tiny compressed texture workaround
const GLuint effectiveBaseLevel = textureState.getEffectiveBaseLevel();
const bool swizzleRequired = textureState.swizzleRequired();
@@ -330,6 +331,7 @@
{
ASSERT(mipLevel >= 0 && mipLevel < getLevelCount());
+ ANGLE_TRY(resolveAndReleaseTexture(context));
auto &levelSRVs = (blitSRV) ? mLevelBlitSRVs : mLevelSRVs;
auto &otherLevelSRVs = (blitSRV) ? mLevelSRVs : mLevelBlitSRVs;
@@ -361,6 +363,7 @@
GLint maxLevel,
const d3d11::SharedSRV **outSRV)
{
+ ANGLE_TRY(resolveAndReleaseTexture(context));
unsigned int mipLevels = maxLevel - baseLevel + 1;
// Make sure there's 'mipLevels' mipmap levels below the base level (offset by the top level,
@@ -390,6 +393,7 @@
const gl::ImageUnit &imageUnit,
const d3d11::SharedSRV **outSRV)
{
+ ANGLE_TRY(resolveAndReleaseTexture(context));
// TODO(Xinghua.cao@intel.com): Add solution to handle swizzle required.
ImageKey key(imageUnit.level, (imageUnit.layered == GL_TRUE), imageUnit.layer, imageUnit.access,
imageUnit.format);
@@ -422,6 +426,7 @@
const gl::ImageUnit &imageUnit,
const d3d11::SharedUAV **outUAV)
{
+ ANGLE_TRY(resolveAndReleaseTexture(context));
// TODO(Xinghua.cao@intel.com): Add solution to handle swizzle required.
ImageKey key(imageUnit.level, (imageUnit.layered == GL_TRUE), imageUnit.layer, imageUnit.access,
imageUnit.format);
@@ -458,6 +463,7 @@
angle::Result TextureStorage11::generateSwizzles(const gl::Context *context,
const gl::SwizzleState &swizzleTarget)
{
+ ANGLE_TRY(resolveAndReleaseTexture(context));
for (int level = 0; level < getLevelCount(); level++)
{
// Check if the swizzle for this level is out of date
@@ -519,6 +525,7 @@
{
ASSERT(srcTexture.valid());
+ ANGLE_TRY(resolveAndReleaseTexture(context));
const GLint level = index.getLevelIndex();
markLevelDirty(level);
@@ -583,6 +590,7 @@
{
ASSERT(dstTexture.valid());
+ ANGLE_TRY(resolveAndReleaseTexture(context));
const TextureHelper11 *srcTexture = nullptr;
// If the zero-LOD workaround is active and we want to update a level greater than zero, then we
@@ -642,14 +650,15 @@
{
ASSERT(sourceIndex.getLayerIndex() == destIndex.getLayerIndex());
+ ANGLE_TRY(resolveAndReleaseTexture(context));
markLevelDirty(destIndex.getLevelIndex());
RenderTargetD3D *source = nullptr;
ANGLE_TRY(getRenderTarget(context, sourceIndex, 0, &source));
+ // dest will always have 0 since, we have just released the MS Texture struct
RenderTargetD3D *dest = nullptr;
- GLsizei destSamples = 0;
- ANGLE_TRY(getRenderTarget(context, destIndex, destSamples, &dest));
+ ANGLE_TRY(getRenderTarget(context, destIndex, 0, &dest));
RenderTarget11 *rt11 = GetAs<RenderTarget11>(source);
const d3d11::SharedSRV &sourceSRV = rt11->getBlitShaderResourceView(context);
@@ -696,6 +705,7 @@
{
ASSERT(destStorage);
+ ANGLE_TRY(resolveAndReleaseTexture(context));
const TextureHelper11 *sourceResouce = nullptr;
ANGLE_TRY(getResource(context, &sourceResouce));
@@ -726,6 +736,7 @@
{
ASSERT(!image->isDirty());
+ ANGLE_TRY(resolveAndReleaseTexture(context));
markLevelDirty(index.getLevelIndex());
const TextureHelper11 *resource = nullptr;
@@ -856,6 +867,36 @@
return angle::Result::Continue;
}
+angle::Result TextureStorage11::resolveTextureHelper(const gl::Context *context,
+ const TextureHelper11 &texture)
+{
+ if (mMSTexInfo)
+ {
+ gl::ImageIndex indexSS = gl::ImageIndex::Make2D(mMSTexInfo->index.getLevelIndex());
+ UINT subresourceIndexSS;
+ ANGLE_TRY(getSubresourceIndex(context, indexSS, &subresourceIndexSS));
+ // For MS texture level must = 0, layer is the entire level -> 0
+ // and miplevels must = 1. D3D11CalcSubresource(level, layer, miplevels);
+ UINT subresourceIndexMS = D3D11CalcSubresource(0, 0, 1);
+ ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
+ const TextureHelper11 *resource = nullptr;
+ ANGLE_TRY(mMSTexInfo->msTex->getResource(context, &resource));
+ deviceContext->ResolveSubresource(texture.get(), subresourceIndexSS, resource->get(),
+ subresourceIndexMS, texture.getFormat());
+ }
+ return angle::Result::Continue;
+}
+
+angle::Result TextureStorage11::releaseMultisampledTexStorageForLevel(size_t level)
+{
+ if (mMSTexInfo && mMSTexInfo->index.getLevelIndex() == static_cast<int>(level))
+ {
+ mMSTexInfo->msTex.reset();
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+ }
+ return angle::Result::Continue;
+}
+
GLsizei TextureStorage11::getRenderToTextureSamples() const
{
if (mMSTexInfo)
@@ -865,6 +906,58 @@
return 0;
}
+angle::Result TextureStorage11::getMultisampledRenderTarget(const gl::Context *context,
+ const gl::ImageIndex &index,
+ GLsizei samples,
+ RenderTargetD3D **outRT)
+{
+ const int level = index.getLevelIndex();
+ if (mMSTexInfo && level == mMSTexInfo->index.getLevelIndex() &&
+ samples == mMSTexInfo->samples && mMSTexInfo->msTex)
+ {
+ RenderTargetD3D *rt;
+ ANGLE_TRY(mMSTexInfo->msTex->getRenderTarget(context, index, samples, &rt));
+ *outRT = rt;
+ return angle::Result::Continue;
+ }
+ else
+ {
+ // if mMSTexInfo already exists, then we want to resolve and release it
+ // since the mMSTexInfo must be for a different sample count or level
+ ANGLE_TRY(resolveAndReleaseTexture(context));
+ ASSERT(!mMSTexInfo);
+
+ // Now we can create a new object for the correct sample and level
+ GLsizei width = getLevelWidth(level);
+ GLsizei height = getLevelHeight(level);
+ GLenum internalFormat = mFormatInfo.internalFormat;
+ std::unique_ptr<TextureStorage11_2DMultisample> texMS(
+ GetAs<TextureStorage11_2DMultisample>(mRenderer->createTextureStorage2DMultisample(
+ internalFormat, width, height, level, samples, true)));
+
+ // make sure multisample object has the blitted information.
+ gl::Rectangle area(0, 0, width, height);
+ gl::ImageIndex indexSS = gl::ImageIndex::Make2D(level);
+ RenderTargetD3D *readRenderTarget = nullptr;
+ ANGLE_TRY(getRenderTarget(context, index, 0, &readRenderTarget));
+ gl::ImageIndex indexMS = gl::ImageIndex::Make2DMultisample();
+ RenderTargetD3D *drawRenderTarget = nullptr;
+ ANGLE_TRY(texMS->getRenderTarget(context, indexMS, samples, &drawRenderTarget));
+
+ // blit SS -> MS
+ // mask: GL_COLOR_BUFFER_BIT, filter: GL_NEAREST
+ ANGLE_TRY(mRenderer->blitRenderbufferRect(context, area, area, readRenderTarget,
+ drawRenderTarget, GL_NEAREST, nullptr, true,
+ false, false));
+ mMSTexInfo = std::make_unique<MultisampledRenderToTextureInfo>(samples, indexSS);
+ mMSTexInfo->msTex = std::move(texMS);
+ RenderTargetD3D *rt;
+ ANGLE_TRY(mMSTexInfo->msTex->getRenderTarget(context, index, samples, &rt));
+ *outRT = rt;
+ return angle::Result::Continue;
+ }
+}
+
TextureStorage11_2D::TextureStorage11_2D(Renderer11 *renderer, SwapChain11 *swapchain)
: TextureStorage11(renderer,
D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE,
@@ -1130,6 +1223,7 @@
angle::Result TextureStorage11_2D::ensureTextureExists(const gl::Context *context, int mipLevels)
{
// If mMipLevels = 1 then always use mTexture rather than mLevelZeroTexture.
+ ANGLE_TRY(resolveAndReleaseTexture(context));
bool useLevelZeroTexture = mRenderer->getFeatures().zeroMaxLodWorkaround.enabled
? (mipLevels == 1) && (mMipLevels > 1)
: false;
@@ -1180,6 +1274,16 @@
const int level = index.getLevelIndex();
ASSERT(level >= 0 && level < getLevelCount());
+ bool needMS = samples > 0;
+ if (needMS)
+ {
+ return getMultisampledRenderTarget(context, index, samples, outRT);
+ }
+ else
+ {
+ ANGLE_TRY(resolveAndReleaseTexture(context));
+ }
+
// In GL ES 2.0, the application can only render to level zero of the texture (Section 4.4.3 of
// the GLES 2.0 spec, page 113 of version 2.0.25). Other parts of TextureStorage11_2D could
// create RTVs on non-zero levels of the texture (e.g. generateMipmap).
@@ -1448,6 +1552,21 @@
return angle::Result::Continue;
}
+angle::Result TextureStorage11_2D::resolveAndReleaseTexture(const gl::Context *context)
+{
+ if (mMSTexInfo)
+ {
+ if (mMSTexInfo->msTex)
+ {
+ ANGLE_TRY(resolveTextureHelper(context, mTexture));
+ mRenderTarget[mMSTexInfo->index.getLevelIndex()].reset(nullptr);
+ }
+ mMSTexInfo.reset(nullptr);
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+ }
+ return angle::Result::Continue;
+}
+
TextureStorage11_External::TextureStorage11_External(
Renderer11 *renderer,
egl::Stream *stream,
@@ -2170,6 +2289,7 @@
angle::Result TextureStorage11_Cube::ensureTextureExists(const gl::Context *context, int mipLevels)
{
// If mMipLevels = 1 then always use mTexture rather than mLevelZeroTexture.
+ ANGLE_TRY(resolveAndReleaseTexture(context));
bool useLevelZeroTexture = mRenderer->getFeatures().zeroMaxLodWorkaround.enabled
? (mipLevels == 1) && (mMipLevels > 1)
: false;
@@ -2241,6 +2361,16 @@
ASSERT(level >= 0 && level < getLevelCount());
ASSERT(faceIndex >= 0 && faceIndex < static_cast<GLint>(gl::kCubeFaceCount));
+ bool needMS = samples > 0;
+ if (needMS)
+ {
+ return getMultisampledRenderTarget(context, index, samples, outRT);
+ }
+ else
+ {
+ ANGLE_TRY(resolveAndReleaseTexture(context));
+ }
+
Context11 *context11 = GetImplAs<Context11>(context);
if (!mRenderTarget[faceIndex][level])
@@ -2534,6 +2664,23 @@
return angle::Result::Continue;
}
+angle::Result TextureStorage11_Cube::resolveAndReleaseTexture(const gl::Context *context)
+{
+ if (mMSTexInfo)
+ {
+ if (mMSTexInfo->msTex)
+ {
+ ANGLE_TRY(resolveTextureHelper(context, mTexture));
+ const int faceIndex = mMSTexInfo->index.cubeMapFaceIndex();
+ const int level = mMSTexInfo->index.getLevelIndex();
+ mRenderTarget[faceIndex][level].reset(nullptr);
+ }
+ mMSTexInfo.reset(nullptr);
+ onStateChange(angle::SubjectMessage::ContentsChanged);
+ }
+ return angle::Result::Continue;
+}
+
TextureStorage11_3D::TextureStorage11_3D(Renderer11 *renderer,
GLenum internalformat,
bool renderTarget,
diff --git a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
index f85444e..e47e132 100644
--- a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
+++ b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.h
@@ -184,6 +184,14 @@
// Clear all cached non-swizzle SRVs and invalidate the swizzle cache.
void clearSRVCache();
+ // Helper for resolving MS shadowed texture
+ angle::Result resolveTextureHelper(const gl::Context *context, const TextureHelper11 &texture);
+ angle::Result releaseMultisampledTexStorageForLevel(size_t level) override;
+ angle::Result getMultisampledRenderTarget(const gl::Context *context,
+ const gl::ImageIndex &index,
+ GLsizei samples,
+ RenderTargetD3D **outRT);
+
Renderer11 *mRenderer;
int mTopLevel;
unsigned int mMipLevels;
@@ -298,6 +306,8 @@
angle::Result ensureTextureExists(const gl::Context *context, int mipLevels);
+ angle::Result resolveAndReleaseTexture(const gl::Context *context) override;
+
private:
angle::Result createSRVForSampler(const gl::Context *context,
int baseLevel,
@@ -539,6 +549,8 @@
angle::Result ensureTextureExists(const gl::Context *context, int mipLevels);
+ angle::Result resolveAndReleaseTexture(const gl::Context *context) override;
+
private:
angle::Result createSRVForSampler(const gl::Context *context,
int baseLevel,
diff --git a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
index 085be7f..b58a55d 100644
--- a/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/renderer11_utils.cpp
@@ -1649,16 +1649,17 @@
extensions->copyTexture = true;
extensions->copyCompressedTexture = true;
extensions->textureStorageMultisample2DArray = true;
- extensions->multiviewMultisample = ((extensions->multiview || extensions->multiview2) &&
+ extensions->multiviewMultisample = ((extensions->multiview || extensions->multiview2) &&
extensions->textureStorageMultisample2DArray);
- extensions->copyTexture3d = true;
- extensions->textureBorderClamp = true;
- extensions->textureMultisample = true;
- extensions->provokingVertex = true;
- extensions->blendFuncExtended = true;
- extensions->maxDualSourceDrawBuffers = 1;
- extensions->texture3DOES = true;
- extensions->baseVertexBaseInstance = true;
+ extensions->copyTexture3d = true;
+ extensions->textureBorderClamp = true;
+ extensions->textureMultisample = true;
+ extensions->provokingVertex = true;
+ extensions->blendFuncExtended = true;
+ extensions->maxDualSourceDrawBuffers = 1;
+ extensions->texture3DOES = true;
+ extensions->baseVertexBaseInstance = true;
+ extensions->multisampledRenderToTexture = true;
// D3D11 cannot support reading depth texture as a luminance texture.
// It treats it as a red-channel-only texture.
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index 9fbe4ab..095055c 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -568,7 +568,11 @@
GLsizei readAttachmentSamples = 0;
if (colorReadAttachment != nullptr)
{
- readAttachmentSamples = colorReadAttachment->getSamples();
+ // Blitting requires that the textures be single sampled. getSamples will return
+ // emulated sample number, but the EXT_multisampled_render_to_texture extension will
+ // take care of resolving the texture, so even if emulated samples > 0, we should still
+ // be able to blit as long as the underlying resource samples is single sampled.
+ readAttachmentSamples = colorReadAttachment->getResourceSamples();
}
bool needManualColorBlit = false;
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index 42e7446..f262f0e 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -635,7 +635,8 @@
const bool blitDepthBuffer = (mask & GL_DEPTH_BUFFER_BIT) != 0;
const bool blitStencilBuffer = (mask & GL_STENCIL_BUFFER_BIT) != 0;
- const bool isResolve = srcFramebuffer->getCachedSamples(context) > 1;
+ const bool isResolve =
+ srcFramebuffer->getCachedSamples(context, gl::AttachmentSampleType::Resource) > 1;
FramebufferVk *srcFramebufferVk = vk::GetImpl(srcFramebuffer);
const bool srcFramebufferFlippedY = contextVk->isViewportFlipEnabledForReadFBO();
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 13d1eae..bbf30cc 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -1330,7 +1330,9 @@
return false;
}
- if (!ValidateFramebufferNotMultisampled(context, drawFramebuffer))
+ // Not allow blitting to MS buffers, therefore if renderToTextureSamples exist,
+ // consider it MS. needResourceSamples = false
+ if (!ValidateFramebufferNotMultisampled(context, drawFramebuffer, false))
{
return false;
}
@@ -2481,8 +2483,10 @@
return false;
}
+ // needResourceSamples = true. Treat renderToTexture textures as single sample since they will
+ // be resolved before copying
if (!readFramebuffer->isDefault() &&
- !ValidateFramebufferNotMultisampled(context, readFramebuffer))
+ !ValidateFramebufferNotMultisampled(context, readFramebuffer, true))
{
return false;
}
@@ -3900,6 +3904,14 @@
}
break;
+ case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT:
+ if (!context->getExtensions().multisampledRenderToTexture)
+ {
+ context->validationError(GL_INVALID_ENUM, kEnumNotSupported);
+ return false;
+ }
+ break;
+
case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
if (clientVersion < 3 && !context->getExtensions().sRGB)
{
@@ -5446,8 +5458,10 @@
return false;
}
+ // needIntrinsic = true. Treat renderToTexture textures as single sample since they will be
+ // resolved before reading.
if (!readFramebuffer->isDefault() &&
- !ValidateFramebufferNotMultisampled(context, readFramebuffer))
+ !ValidateFramebufferNotMultisampled(context, readFramebuffer, true))
{
return false;
}
@@ -6302,9 +6316,13 @@
return true;
}
-bool ValidateFramebufferNotMultisampled(Context *context, Framebuffer *framebuffer)
+bool ValidateFramebufferNotMultisampled(Context *context,
+ Framebuffer *framebuffer,
+ bool needResourceSamples)
{
- if (framebuffer->getSamples(context) != 0)
+ int samples = needResourceSamples ? framebuffer->getResourceSamples(context)
+ : framebuffer->getSamples(context);
+ if (samples != 0)
{
context->validationError(GL_INVALID_OPERATION, kInvalidMultisampledFramebufferOperation);
return false;
diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h
index 3fd6d89..04164d9 100644
--- a/src/libANGLE/validationES.h
+++ b/src/libANGLE/validationES.h
@@ -614,7 +614,9 @@
GLsizei bufSize,
GLsizei *numParams);
-bool ValidateFramebufferNotMultisampled(Context *context, Framebuffer *framebuffer);
+bool ValidateFramebufferNotMultisampled(Context *context,
+ Framebuffer *framebuffer,
+ bool needResourceSamples);
bool ValidateMultitextureUnit(Context *context, GLenum texture);
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index d1ffe9b..821bc66 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -7350,6 +7350,78 @@
GLint level,
GLsizei samples)
{
+ if (!context->getExtensions().multisampledRenderToTexture)
+ {
+ context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
+ return false;
+ }
+
+ if (samples < 0)
+ {
+ return false;
+ }
+
+ // EXT_multisampled_render_to_texture states that the value of samples
+ // must be less than or equal to MAX_SAMPLES_EXT (Context::getCaps().maxSamples)
+ // otherwise GL_INVALID_VALUE is generated.
+ if (static_cast<GLuint>(samples) > context->getCaps().maxSamples)
+ {
+ context->validationError(GL_INVALID_VALUE, kSamplesOutOfRange);
+ return false;
+ }
+
+ if (!ValidFramebufferTarget(context, target))
+ {
+ context->validationError(GL_INVALID_ENUM, kInvalidFramebufferTarget);
+ return false;
+ }
+
+ if (attachment != GL_COLOR_ATTACHMENT0)
+ {
+ context->validationError(GL_INVALID_ENUM, kInvalidAttachment);
+ return false;
+ }
+
+ TextureTarget textargetPacked = FromGLenum<TextureTarget>(textarget);
+ if (!ValidTexture2DDestinationTarget(context, textargetPacked))
+ {
+ context->validationError(GL_INVALID_ENUM, kInvalidTextureTarget);
+ return false;
+ }
+
+ if (texture != 0)
+ {
+ TextureID texturePacked = FromGL<TextureID>(texture);
+ Texture *tex = context->getTexture(texturePacked);
+
+ if (tex == nullptr)
+ {
+ context->validationError(GL_INVALID_OPERATION, kMissingTexture);
+ return false;
+ }
+
+ if (level < 0)
+ {
+ context->validationError(GL_INVALID_VALUE, kInvalidMipLevel);
+ return false;
+ }
+
+ // EXT_multisampled_render_to_texture returns INVALID_OPERATION when a sample number higher
+ // than the maximum sample number supported by this format is passed.
+ // The TextureCaps::getMaxSamples method is only guarenteed to be valid when the context is
+ // ES3.
+ if (context->getClientMajorVersion() >= 3)
+ {
+ GLenum internalformat = tex->getFormat(textargetPacked, level).info->internalFormat;
+ const TextureCaps &formatCaps = context->getTextureCaps().get(internalformat);
+ if (static_cast<GLuint>(samples) > formatCaps.getMaxSamples())
+ {
+ context->validationError(GL_INVALID_OPERATION, kSamplesOutOfRange);
+ return false;
+ }
+ }
+ }
+
return true;
}
@@ -7360,6 +7432,40 @@
GLsizei width,
GLsizei height)
{
+ if (!context->getExtensions().multisampledRenderToTexture)
+ {
+ context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
+ return false;
+ }
+ if (!ValidateRenderbufferStorageParametersBase(context, target, samples, internalformat, width,
+ height))
+ {
+ return false;
+ }
+
+ // EXT_multisampled_render_to_texture states that the value of samples
+ // must be less than or equal to MAX_SAMPLES_EXT (Context::getCaps().maxSamples)
+ // otherwise GL_INVALID_VALUE is generated.
+ if (static_cast<GLuint>(samples) > context->getCaps().maxSamples)
+ {
+ context->validationError(GL_INVALID_VALUE, kSamplesOutOfRange);
+ return false;
+ }
+
+ // EXT_multisampled_render_to_texture returns GL_OUT_OF_MEMORY on failure to create
+ // the specified storage. This is different than ES 3.0 in which a sample number higher
+ // than the maximum sample number supported by this format generates a GL_INVALID_VALUE.
+ // The TextureCaps::getMaxSamples method is only guarenteed to be valid when the context is ES3.
+ if (context->getClientMajorVersion() >= 3)
+ {
+ const TextureCaps &formatCaps = context->getTextureCaps().get(internalformat);
+ if (static_cast<GLuint>(samples) > formatCaps.getMaxSamples())
+ {
+ context->validationError(GL_OUT_OF_MEMORY, kSamplesOutOfRange);
+ return false;
+ }
+ }
+
return true;
}
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
index f399686..5a03bf5 100644
--- a/src/libANGLE/validationES3.cpp
+++ b/src/libANGLE/validationES3.cpp
@@ -984,7 +984,10 @@
return false;
}
- if (!framebuffer->isDefault() && !ValidateFramebufferNotMultisampled(context, framebuffer))
+ // needIntrinsic = true. Treat renderToTexture textures as single sample since they will be
+ // resolved before copying
+ if (!framebuffer->isDefault() &&
+ !ValidateFramebufferNotMultisampled(context, framebuffer, true))
{
return false;
}
diff --git a/src/tests/angle_end2end_tests.gni b/src/tests/angle_end2end_tests.gni
index 9b7bed6..206be02 100644
--- a/src/tests/angle_end2end_tests.gni
+++ b/src/tests/angle_end2end_tests.gni
@@ -81,6 +81,7 @@
"gl_tests/MipmapTest.cpp",
"gl_tests/MultiDrawTest.cpp",
"gl_tests/MultisampleCompatibilityTest.cpp",
+ "gl_tests/MultisampledRenderToTextureTest.cpp",
"gl_tests/MultisampleTest.cpp",
"gl_tests/MultithreadingTest.cpp",
"gl_tests/MultiviewDrawTest.cpp",
diff --git a/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp b/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp
new file mode 100644
index 0000000..cb90c5b
--- /dev/null
+++ b/src/tests/gl_tests/MultisampledRenderToTextureTest.cpp
@@ -0,0 +1,738 @@
+//
+// 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.
+//
+
+// MultisampledRenderToTextureTest: Tests of EXT_multisampled_render_to_texture extension
+
+#include "test_utils/ANGLETest.h"
+#include "test_utils/gl_raii.h"
+
+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:
+ MultisampledRenderToTextureTest()
+ {
+ setWindowWidth(64);
+ setWindowHeight(64);
+ setConfigRedBits(8);
+ setConfigGreenBits(8);
+ setConfigBlueBits(8);
+ setConfigAlphaBits(8);
+ }
+
+ void testSetUp() override {}
+
+ void testTearDown() override {}
+
+ void setupCopyTexProgram()
+ {
+ mCopyTextureProgram.makeRaster(kVS, kFS);
+ ASSERT_GL_TRUE(mCopyTextureProgram.valid());
+
+ mCopyTextureUniformLocation = glGetUniformLocation(mCopyTextureProgram, "tex");
+
+ ASSERT_GL_NO_ERROR();
+ }
+
+ void verifyResults(GLuint texture,
+ GLubyte data[4],
+ GLint fboSize,
+ GLint xs,
+ GLint ys,
+ GLint xe,
+ GLint ye)
+ {
+ glViewport(0, 0, fboSize, fboSize);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ // Draw a quad with the target texture
+ glUseProgram(mCopyTextureProgram);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glUniform1i(mCopyTextureUniformLocation, 0);
+
+ drawQuad(mCopyTextureProgram, "position", 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);
+ }
+
+ void clearAndDrawQuad(GLuint program, GLsizei viewportWidth, GLsizei viewportHeight)
+ {
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, viewportWidth, viewportHeight);
+ ASSERT_GL_NO_ERROR();
+
+ drawQuad(program, "position", 0.0f);
+ }
+
+ GLProgram mCopyTextureProgram;
+ GLint mCopyTextureUniformLocation = -1;
+};
+
+class MultisampledRenderToTextureES3Test : public MultisampledRenderToTextureTest
+{};
+
+// Checking against invalid parameters for RenderbufferStorageMultisampleEXT.
+TEST_P(MultisampledRenderToTextureTest, RenderbufferParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+
+ // Positive test case
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_NO_ERROR();
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ // Samples too large
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, samples + 1, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Renderbuffer size too large
+ GLint maxSize;
+ glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 2, GL_DEPTH_COMPONENT16, maxSize + 1,
+ maxSize);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 2, GL_DEPTH_COMPONENT16, maxSize,
+ maxSize + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, 64, 64);
+ GLint param = 0;
+ glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES_EXT, ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // RENDERBUFFER_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more
+ // than the next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+}
+
+// Checking against invalid parameters for FramebufferTexture2DMultisampleEXT.
+TEST_P(MultisampledRenderToTextureTest, Texture2DParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ // Positive test case
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Attachment not COLOR_ATTACHMENT0
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Target not framebuffer
+ glFramebufferTexture2DMultisampleEXT(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ // Samples too large
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, samples + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ GLint param = 0;
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT, ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // TEXTURE_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more than the
+ // next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+}
+
+// Checking against invalid parameters for FramebufferTexture2DMultisampleEXT (cubemap).
+TEST_P(MultisampledRenderToTextureTest, TextureCubeMapParameterCheck)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+ for (GLenum face = 0; face < 6; face++)
+ {
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, 64, 64, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+ }
+
+ GLint samples;
+ glGetIntegerv(GL_MAX_SAMPLES_EXT, &samples);
+ ASSERT_GL_NO_ERROR();
+ EXPECT_GE(samples, 1);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ for (GLenum face = 0; face < 6; face++)
+ {
+ // Positive test case
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Attachment not COLOR_ATTACHMENT0
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Target not framebuffer
+ glFramebufferTexture2DMultisampleEXT(GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ ASSERT_GL_ERROR(GL_INVALID_ENUM);
+
+ // Samples too large
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0,
+ samples + 1);
+ ASSERT_GL_ERROR(GL_INVALID_VALUE);
+
+ // Retrieving samples
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, texture, 0, 4);
+ GLint param = 0;
+ glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT,
+ ¶m);
+ // GE because samples may vary base on implementation. Spec says "the resulting value for
+ // TEXTURE_SAMPLES_EXT is guaranteed to be greater than or equal to samples and no more than
+ // the next larger sample count supported by the implementation"
+ EXPECT_GE(param, 4);
+ }
+}
+
+// Checking for framebuffer completeness using extension methods.
+TEST_P(MultisampledRenderToTextureTest, FramebufferCompleteness)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+
+ // Checking that Renderbuffer and texture2d having different number of samples results
+ // in a FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 8, GL_DEPTH_COMPONENT16, 64, 64);
+ ASSERT_GL_NO_ERROR();
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
+ EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE,
+ glCheckFramebufferStatus(GL_FRAMEBUFFER));
+}
+
+// Draw test with color attachment only.
+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;
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set viewport and clear to black
+ glViewport(0, 0, size, size);
+ 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);
+ glUseProgram(program);
+ GLint positionLocation = glGetAttribLocation(program, "position");
+ ASSERT_NE(-1, positionLocation);
+
+ setupQuadVertexBuffer(0.5f, 0.5f);
+ glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(positionLocation);
+
+ // Draw green square
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ // Set up Red square program
+ ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
+ glUseProgram(program2);
+ GLint positionLocation2 = glGetAttribLocation(program2, "position");
+ ASSERT_NE(-1, positionLocation2);
+
+ setupQuadVertexBuffer(0.5f, 0.75f);
+ glVertexAttribPointer(positionLocation2, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ // Draw red square
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::red);
+
+ glDisableVertexAttribArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+// Draw test using both color and depth attachments.
+TEST_P(MultisampledRenderToTextureTest, 2DColorDepthMultisampleDrawTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 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);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ GLRenderbuffer renderbuffer;
+ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, size, size);
+ 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);
+ 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);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_GREATER);
+ glUseProgram(program);
+ GLint positionLocation = glGetAttribLocation(program, "position");
+ ASSERT_NE(-1, positionLocation);
+
+ setupQuadVertexBuffer(0.8f, 0.5f);
+ glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(positionLocation);
+
+ // Tests that TRIANGLES works.
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ // Draw red square behind green square
+ ANGLE_GL_PROGRAM(program2, kBasicVertexShader, kRedFragmentShader);
+ glUseProgram(program2);
+ GLint positionLocation2 = glGetAttribLocation(program2, "position");
+ ASSERT_NE(-1, positionLocation2);
+
+ setupQuadVertexBuffer(0.7f, 1.0f);
+ glVertexAttribPointer(positionLocation2, 3, GL_FLOAT, GL_FALSE, 0, 0);
+
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ ASSERT_GL_NO_ERROR();
+ glDisable(GL_DEPTH_TEST);
+
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+ EXPECT_PIXEL_COLOR_EQ(size / 2, size / 2, GLColor::green);
+
+ glDisableVertexAttribArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+// Read pixels with pack buffer. ES3+.
+TEST_P(MultisampledRenderToTextureES3Test, MultisampleReadPixelsTest)
+{
+ 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;
+ GLTexture texture;
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size, size);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Set viewport and clear to red
+ glViewport(0, 0, size, size);
+ glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ // 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);
+ ASSERT_GL_NO_ERROR();
+
+ // Retrieving pixel color
+ void *mappedPtr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, 32, GL_MAP_READ_BIT);
+ GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
+ EXPECT_GL_NO_ERROR();
+
+ EXPECT_EQ(GLColor::red, dataColor[0]);
+
+ glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+ EXPECT_GL_NO_ERROR();
+}
+
+// CopyTexImage from a multisampled texture functionality test.
+TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexImageTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 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);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ // Set color for framebuffer
+ glClearColor(0.25f, 1.0f, 0.75f, 0.5f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ GLTexture copyToTex;
+ glBindTexture(GL_TEXTURE_2D, copyToTex);
+
+ // Disable mipmapping
+ 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);
+ ASSERT_GL_NO_ERROR();
+
+ GLubyte expected[4] = {64, 255, 191, 255};
+ verifyResults(copyToTex, expected, size, 0, 0, size, size);
+}
+
+// CopyTexSubImage from a multisampled texture functionality test.
+TEST_P(MultisampledRenderToTextureTest, MultisampleCopyTexSubImageTest)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
+ GLsizei size = 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);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer copyFBO0;
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO0);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+
+ // Set color for
+ glClearColor(0.25f, 1.0f, 0.75f, 0.5f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ // 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);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ GLFramebuffer copyFBO1;
+ glBindFramebuffer(GL_FRAMEBUFFER, copyFBO1);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture1, 0, 4);
+
+ // Set color for
+ glClearColor(1.0f, 0.75f, 0.5f, 0.25f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ASSERT_GL_NO_ERROR();
+
+ GLTexture copyToTex;
+ glBindTexture(GL_TEXTURE_2D, copyToTex);
+
+ // Disable mipmapping
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // 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);
+ ASSERT_GL_NO_ERROR();
+
+ GLubyte expected0[4] = {64, 255, 191, 255};
+ verifyResults(copyToTex, expected0, size, 0, 0, size, size);
+
+ // 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;
+ 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);
+
+ // 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);
+}
+
+// BlitFramebuffer functionality test. ES3+.
+TEST_P(MultisampledRenderToTextureES3Test, MultisampleBlitFramebufferTest)
+{
+ 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;
+
+ // Create multisampled framebuffer to use as source.
+ GLRenderbuffer depthMS;
+ glBindRenderbuffer(GL_RENDERBUFFER, depthMS.get());
+ glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT24, size, size);
+
+ GLTexture colorMS;
+ glBindTexture(GL_TEXTURE_2D, colorMS);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ GLFramebuffer fboMS;
+ glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS.get());
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ colorMS, 0, 4);
+ ASSERT_GL_NO_ERROR();
+
+ // Clear depth to 0.5 and color to green.
+ glClearDepthf(0.5f);
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ glFlush();
+ ASSERT_GL_NO_ERROR();
+
+ // Draw red into the multisampled color buffer.
+ 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);
+ ASSERT_GL_NO_ERROR();
+
+ // Create single sampled framebuffer to use as dest.
+ GLFramebuffer fboSS;
+ 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);
+ 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);
+ ASSERT_GL_NO_ERROR();
+
+ // Bind SS to READ so we can readPixels from it
+ glBindFramebuffer(GL_FRAMEBUFFER, fboSS.get());
+
+ 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);
+ 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()
+{
+ gl_FragColor = texture2D(uTexture, vTexCoord);
+})";
+
+ GLProgram m2DProgram;
+ m2DProgram.makeRaster(kVS, kFS);
+ ASSERT_GL_TRUE(m2DProgram.valid());
+
+ ASSERT_GL_NO_ERROR();
+
+ // 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);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ GLFramebuffer FBO;
+ glBindFramebuffer(GL_FRAMEBUFFER, FBO);
+ glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ texture, 0, 4);
+ 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);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ ASSERT_GL_NO_ERROR();
+
+ // Generate mipmap
+ glGenerateMipmap(GL_TEXTURE_2D);
+ ASSERT_GL_NO_ERROR();
+
+ 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);
+
+ // Use mip level 1
+ clearAndDrawQuad(m2DProgram, size / 2, size / 2);
+ EXPECT_PIXEL_COLOR_EQ(size / 4, size / 4, GLColor::blue);
+
+ // Use mip level 2
+ clearAndDrawQuad(m2DProgram, size / 4, size / 4);
+ EXPECT_PIXEL_COLOR_EQ(size / 8, size / 8, GLColor::blue);
+
+ 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());
+} // namespace