Commit c5181706 by Luc Ferron Committed by Commit Bot

Vulkan: generateMipmaps for 2D textures

- This implements cpu-only mipmaps generation. To do in the next CLs: - Cube textures support - GPU faster path with vkCmdBlitImage - Find the bug with clearing of level 0 mips. - Investigate the intel linux specific bug. Bug: angleproject:2502 Change-Id: Iecd73cb45ebc9c79ce6291c4529eb27330b51e2c Reviewed-on: https://chromium-review.googlesource.com/1064194 Commit-Queue: Luc Ferron <lucferron@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent 0ed2aa23
......@@ -10,6 +10,7 @@
#include "libANGLE/renderer/vulkan/TextureVk.h"
#include "common/debug.h"
#include "image_util/generatemip.inl"
#include "libANGLE/Context.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/FramebufferVk.h"
......@@ -234,6 +235,17 @@ gl::Error PixelBuffer::stageSubresourceUpdateFromFramebuffer(const gl::Context *
return gl::NoError();
}
gl::Error PixelBuffer::allocate(RendererVk *renderer,
size_t sizeInBytes,
uint8_t **ptrOut,
VkBuffer *handleOut,
uint32_t *offsetOut,
bool *newBufferAllocatedOut)
{
return mStagingBuffer.allocate(renderer, sizeInBytes, ptrOut, handleOut, offsetOut,
newBufferAllocatedOut);
}
vk::Error PixelBuffer::flushUpdatesToImage(RendererVk *renderer,
vk::ImageHelper *image,
vk::CommandBuffer *commandBuffer)
......@@ -272,6 +284,85 @@ bool PixelBuffer::empty() const
return mSubresourceUpdates.empty();
}
gl::Error PixelBuffer::stageSubresourceUpdateAndGetData(RendererVk *renderer,
size_t allocationSize,
const gl::ImageIndex &imageIndex,
const gl::Extents &extents,
const gl::Offset &offset,
uint8_t **destData)
{
VkBuffer bufferHandle;
uint32_t stagingOffset = 0;
bool newBufferAllocated = false;
ANGLE_TRY(mStagingBuffer.allocate(renderer, allocationSize, destData, &bufferHandle,
&stagingOffset, &newBufferAllocated));
VkBufferImageCopy copy;
copy.bufferOffset = static_cast<VkDeviceSize>(stagingOffset);
copy.bufferRowLength = extents.width;
copy.bufferImageHeight = extents.height;
copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy.imageSubresource.mipLevel = imageIndex.getLevelIndex();
copy.imageSubresource.baseArrayLayer = imageIndex.hasLayer() ? imageIndex.getLayerIndex() : 0;
copy.imageSubresource.layerCount = imageIndex.getLayerCount();
gl_vk::GetOffset(offset, &copy.imageOffset);
gl_vk::GetExtent(extents, &copy.imageExtent);
mSubresourceUpdates.emplace_back(bufferHandle, copy);
return gl::NoError();
}
gl::Error TextureVk::generateMipmapLevels(ContextVk *contextVk,
const angle::Format &sourceFormat,
GLuint firstMipLevel,
GLuint maxMipLevel,
const size_t sourceWidth,
const size_t sourceHeight,
const size_t sourceRowPitch,
uint8_t *sourceData)
{
RendererVk *renderer = contextVk->getRenderer();
size_t previousLevelWidth = sourceWidth;
size_t previousLevelHeight = sourceHeight;
uint8_t *previousLevelData = sourceData;
size_t previousLevelRowPitch = sourceRowPitch;
for (GLuint currentMipLevel = firstMipLevel; currentMipLevel <= maxMipLevel; currentMipLevel++)
{
// Compute next level width and height.
size_t mipWidth = std::max<size_t>(1, previousLevelWidth >> 1);
size_t mipHeight = std::max<size_t>(1, previousLevelHeight >> 1);
// With the width and height of the next mip, we can allocate the next buffer we need.
uint8_t *destData = nullptr;
size_t destRowPitch = mipWidth * sourceFormat.pixelBytes;
size_t mipAllocationSize = destRowPitch * mipHeight;
gl::Extents mipLevelExtents(static_cast<int>(mipWidth), static_cast<int>(mipHeight), 1);
ANGLE_TRY(mPixelBuffer.stageSubresourceUpdateAndGetData(
renderer, mipAllocationSize,
gl::ImageIndex::MakeFromType(mState.getType(), currentMipLevel, 0, 1), mipLevelExtents,
gl::Offset(), &destData));
// Generate the mipmap into that new buffer
sourceFormat.mipGenerationFunction(previousLevelWidth, previousLevelHeight, 1,
previousLevelData, previousLevelRowPitch, 0, destData,
destRowPitch, 0);
// Swap for the next iteration
previousLevelWidth = mipWidth;
previousLevelHeight = mipHeight;
previousLevelData = destData;
previousLevelRowPitch = destRowPitch;
}
return gl::NoError();
}
PixelBuffer::SubresourceUpdate::SubresourceUpdate() : bufferHandle(VK_NULL_HANDLE)
{
}
......@@ -303,7 +394,6 @@ gl::Error TextureVk::onDestroy(const gl::Context *context)
renderer->releaseResource(*this, &mSampler);
mPixelBuffer.release(renderer);
return gl::NoError();
}
......@@ -463,7 +553,7 @@ vk::Error TextureVk::getCommandBufferForWrite(RendererVk *renderer,
vk::CommandBuffer **commandBufferOut)
{
updateQueueSerial(renderer->getCurrentQueueSerial());
ANGLE_TRY(beginWriteResource(renderer, commandBufferOut));
ANGLE_TRY(appendWriteResource(renderer, commandBufferOut));
return vk::NoError();
}
......@@ -501,8 +591,77 @@ gl::Error TextureVk::setImageExternal(const gl::Context *context,
gl::Error TextureVk::generateMipmap(const gl::Context *context)
{
UNIMPLEMENTED();
return gl::InternalError();
ContextVk *contextVk = vk::GetImpl(context);
vk::CommandBuffer *commandBuffer = nullptr;
RendererVk *renderer = contextVk->getRenderer();
getCommandBufferForWrite(renderer, &commandBuffer);
// Some data is pending, or the image has not been defined at all yet
if (!mImage.valid())
{
// lets initialize the image so we can generate the next levels.
if (!mPixelBuffer.empty())
{
ANGLE_TRY(ensureImageInitialized(renderer));
ASSERT(mImage.valid());
}
else
{
// There is nothing to generate if there is nothing uploaded so far.
return gl::NoError();
}
}
// Before we loop to generate all the next levels, we can get the source level and copy it to a
// buffer.
const angle::Format &angleFormat = mImage.getFormat().textureFormat();
VkBuffer baseLevelBufferHandle = VK_NULL_HANDLE;
uint8_t *baseLevelBuffer = nullptr;
bool newBufferAllocated = false;
uint32_t baseLevelBufferOffset = 0;
const gl::Extents baseLevelExtents = mImage.getExtents();
GLuint sourceRowPitch = baseLevelExtents.width * angleFormat.pixelBytes;
size_t baseLevelAllocationSize = sourceRowPitch * baseLevelExtents.height;
ANGLE_TRY(mPixelBuffer.allocate(renderer, baseLevelAllocationSize, &baseLevelBuffer,
&baseLevelBufferHandle, &baseLevelBufferOffset,
&newBufferAllocated));
VkBufferImageCopy region;
region.bufferImageHeight = baseLevelExtents.height;
region.bufferOffset = static_cast<VkDeviceSize>(baseLevelBufferOffset);
region.bufferRowLength = baseLevelExtents.width;
region.imageExtent.width = baseLevelExtents.width;
region.imageExtent.height = baseLevelExtents.height;
region.imageExtent.depth = 1;
region.imageOffset.x = 0;
region.imageOffset.y = 0;
region.imageOffset.z = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageSubresource.mipLevel = mState.getEffectiveBaseLevel();
mImage.changeLayoutWithStages(VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, commandBuffer);
commandBuffer->copyImageToBuffer(mImage.getImage(), mImage.getCurrentLayout(),
baseLevelBufferHandle, 1, &region);
ANGLE_TRY(renderer->finish(context));
// We're changing this textureVk content, make sure we let the graph know.
onResourceChanged(renderer);
// We now have the base level available to be manipulated in the baseLevelBuffer pointer.
// Generate all the missing mipmaps with the slow path. We can optimize with vkCmdBlitImage
// later.
ANGLE_TRY(generateMipmapLevels(contextVk, angleFormat, mState.getEffectiveBaseLevel() + 1,
mState.getMipmapMaxLevel(), baseLevelExtents.width,
baseLevelExtents.height, sourceRowPitch, baseLevelBuffer));
return gl::NoError();
}
gl::Error TextureVk::setBaseLevel(const gl::Context *context, GLuint baseLevel)
......
......@@ -35,6 +35,13 @@ class PixelBuffer final : angle::NonCopyable
GLenum type,
const uint8_t *pixels);
gl::Error stageSubresourceUpdateAndGetData(RendererVk *renderer,
size_t allocationSize,
const gl::ImageIndex &imageIndex,
const gl::Extents &extents,
const gl::Offset &offset,
uint8_t **destData);
gl::Error stageSubresourceUpdateFromFramebuffer(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Rectangle &sourceArea,
......@@ -43,6 +50,15 @@ class PixelBuffer final : angle::NonCopyable
const gl::InternalFormat &formatInfo,
FramebufferVk *framebufferVk);
// This will use the underlying dynamic buffer to allocate some memory to be used as a src or
// dst.
gl::Error allocate(RendererVk *renderer,
size_t sizeInBytes,
uint8_t **ptrOut,
VkBuffer *handleOut,
uint32_t *offsetOut,
bool *newBufferAllocatedOut);
vk::Error flushUpdatesToImage(RendererVk *renderer,
vk::ImageHelper *image,
vk::CommandBuffer *commandBuffer);
......@@ -159,6 +175,15 @@ class TextureVk : public TextureImpl, public vk::CommandGraphResource
vk::Error ensureImageInitialized(RendererVk *renderer);
private:
gl::Error generateMipmapLevels(ContextVk *contextVk,
const angle::Format &sourceFormat,
GLuint firstMipLevel,
GLuint maxMipLevel,
size_t sourceWidth,
size_t sourceHeight,
size_t sourceRowPitch,
uint8_t *sourceData);
gl::Error copySubImageImpl(const gl::Context *context,
const gl::ImageIndex &index,
const gl::Offset &destOffset,
......
......@@ -237,7 +237,6 @@
2593 VULKAN : dEQP-GLES2.functional.shaders.invariance.mediump.loop_4 = SKIP
2594 VULKAN : dEQP-GLES2.functional.shaders.fragdata.valid_static_index = SKIP
2595 VULKAN : dEQP-GLES2.functional.shaders.random.all_features.fragment* = SKIP
2161 VULKAN : dEQP-GLES2.functional.texture.mipmap.2d.generate.* = SKIP
2161 VULKAN : dEQP-GLES2.functional.texture.mipmap.cube.generate.* = SKIP
2596 VULKAN : dEQP-GLES2.functional.texture.completeness.2d.extra_level = SKIP
2596 VULKAN : dEQP-GLES2.functional.texture.completeness.cube.extra_level = SKIP
......
......@@ -520,6 +520,10 @@ TEST_P(MipmapTest, DISABLED_ThreeLevelsInitData)
// In particular, on D3D11 Feature Level 9_3 it ensures that both the zero LOD workaround texture AND the 'normal' texture are copied during conversion.
TEST_P(MipmapTest, GenerateMipmapFromInitDataThenRender)
{
// TODO(lucferron): Figure out why we clear all levels when trying to clear level 0.
// http://anglebug.com/2502
ANGLE_SKIP_TEST_IF(IsVulkan());
// Pass in initial data so the texture is blue.
glBindTexture(GL_TEXTURE_2D, mTexture2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, getWindowWidth(), getWindowHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, mLevelZeroBlueInitData);
......@@ -574,6 +578,10 @@ TEST_P(MipmapTest, GenerateMipmapFromInitDataThenRender)
// The test ensures that the zero-level texture is correctly copied into the mipped texture before the mipmaps are generated.
TEST_P(MipmapTest, GenerateMipmapFromRenderedImage)
{
// TODO(lucferron): Figure out why this test is failing only on Intel Linux.
// http://anglebug.com/2502
ANGLE_SKIP_TEST_IF(IsVulkan() && IsIntel());
glBindTexture(GL_TEXTURE_2D, mTexture2D);
// Clear the texture to blue.
clearTextureLevel0(GL_TEXTURE_2D, mTexture2D, 0.0f, 0.0f, 1.0f, 1.0f);
......@@ -602,6 +610,10 @@ TEST_P(MipmapTest, GenerateMipmapFromRenderedImage)
// TODO: This test hits a texture rebind bug in the D3D11 renderer. Fix this.
TEST_P(MipmapTest, RenderOntoLevelZeroAfterGenerateMipmap)
{
// TODO(lucferron): Figure out why we clear all levels when trying to clear level 0.
// http://anglebug.com/2502
ANGLE_SKIP_TEST_IF(IsVulkan());
// TODO(geofflang): Figure out why this is broken on AMD OpenGL
ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL());
......@@ -689,6 +701,10 @@ TEST_P(MipmapTest, MipMapGenerationD3D9Bug)
// works as expected. It tests enabling/disabling mipmaps, generating mipmaps, and rendering to level zero.
TEST_P(MipmapTest, TextureCubeGeneralLevelZero)
{
// TODO(lucferron): Implement mipmaps generation for cube textures
// http://anglebug.com/2502
ANGLE_SKIP_TEST_IF(IsVulkan());
glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
// Draw. Since the negative-Y face's is blue, this should be blue.
......@@ -727,6 +743,10 @@ TEST_P(MipmapTest, TextureCubeGeneralLevelZero)
// This test ensures that rendering to level-zero of a TextureCube works as expected.
TEST_P(MipmapTest, TextureCubeRenderToLevelZero)
{
// TODO(lucferron): Implement mipmaps generation for cube textures
// http://anglebug.com/2502
ANGLE_SKIP_TEST_IF(IsVulkan());
glBindTexture(GL_TEXTURE_CUBE_MAP, mTextureCube);
// Draw. Since the negative-Y face's is blue, this should be blue.
......@@ -1148,5 +1168,6 @@ ANGLE_INSTANTIATE_TEST(MipmapTest,
ES2_OPENGL(),
ES3_OPENGL(),
ES2_OPENGLES(),
ES3_OPENGLES());
ES3_OPENGLES(),
ES2_VULKAN());
ANGLE_INSTANTIATE_TEST(MipmapTestES3, ES3_D3D11(), ES3_OPENGL(), ES3_OPENGLES());
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