Example #1
0
bool Image::preMultiply() {
	if(getSurfaceFormat() != CAIRO_FORMAT_ARGB32) return false;

	for(int p = 0; p < _s * _t; p++) {
		unsigned int  offset = p * 4;
		unsigned char alpha  = _data[offset + 3];

		_data[offset]     = (_data[offset] * alpha) / 255;
		_data[offset + 1] = (_data[offset + 1] * alpha) / 255;
		_data[offset + 2] = (_data[offset + 2] * alpha) / 255;
	}

	dirty();

	return true;
}
Example #2
0
vk::Format
getSurfaceFormat(latte::SQ_DATA_FORMAT format,
                latte::SQ_NUM_FORMAT numFormat,
                latte::SQ_FORMAT_COMP formatComp,
                uint32_t degamma,
                bool forDepthBuffer)
{
   static const vk::Format BADFMT = vk::Format::eUndefined;
   auto pick = [=](vk::Format unorm, vk::Format snorm, vk::Format uint, vk::Format sint, vk::Format srgb, vk::Format scaled)
   {
      auto pickedFormat = getSurfaceFormat(numFormat, formatComp, degamma, unorm, snorm, uint, sint, srgb, scaled);
      if (pickedFormat == BADFMT) {
         decaf_abort(fmt::format("Failed to pick vulkan format for latte format: {} {} {} {}", format, numFormat, formatComp, degamma));
      }
      return pickedFormat;
   };

   bool isSigned = formatComp == latte::SQ_FORMAT_COMP::SIGNED;

   if (forDepthBuffer) {
      /*
      vk::Format::eD32Sfloat;
      vk::Format::eD32SfloatS8Uint
      vk::Format::eD16Unorm;
      vk::Format::eD16UnormS8Uint;
      vk::Format::eD24UnormS8Uint;
      */

      switch (format) {
      case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eD32Sfloat);
      case latte::SQ_DATA_FORMAT::FMT_8_24:
         return pick(vk::Format::eD24UnormS8Uint, BADFMT, BADFMT, BADFMT, BADFMT, BADFMT);

      default:
         decaf_abort("Unexpected depth buffer format for vulkan");
      }
   } else {
      switch (format) {
      case latte::SQ_DATA_FORMAT::FMT_8:
         return pick(vk::Format::eR8Unorm, vk::Format::eR8Snorm, vk::Format::eR8Uint, vk::Format::eR8Sint, vk::Format::eR8Srgb, vk::Format::eR8Sscaled);
      //case latte::SQ_DATA_FORMAT::FMT_4_4:
      //case latte::SQ_DATA_FORMAT::FMT_3_3_2:
      case latte::SQ_DATA_FORMAT::FMT_16:
         return pick(vk::Format::eR16Unorm, vk::Format::eR16Snorm, vk::Format::eR16Uint, vk::Format::eR16Sint, BADFMT, vk::Format::eR16Sscaled);
      case latte::SQ_DATA_FORMAT::FMT_16_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR16Sfloat);
      case latte::SQ_DATA_FORMAT::FMT_8_8:
         return pick(vk::Format::eR8G8Unorm, vk::Format::eR8G8Snorm, vk::Format::eR8G8Uint, vk::Format::eR8G8Sint, vk::Format::eR8G8Srgb, vk::Format::eR8G8Sscaled);
      case latte::SQ_DATA_FORMAT::FMT_5_6_5:
         return pick(vk::Format::eB5G6R5UnormPack16, BADFMT, BADFMT, BADFMT, BADFMT, BADFMT);
      //case latte::SQ_DATA_FORMAT::FMT_6_5_5:
      //case latte::SQ_DATA_FORMAT::FMT_1_5_5_5:
      //case latte::SQ_DATA_FORMAT::FMT_4_4_4_4:
      //case latte::SQ_DATA_FORMAT::FMT_5_5_5_1:
      case latte::SQ_DATA_FORMAT::FMT_32:
         return pick(BADFMT, BADFMT, vk::Format::eR32Uint, vk::Format::eR32Sint, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_32_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR32Sfloat);
      case latte::SQ_DATA_FORMAT::FMT_16_16:
         return pick(vk::Format::eR16G16Unorm, vk::Format::eR16G16Snorm, vk::Format::eR16G16Uint, vk::Format::eR16G16Sint, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_16_16_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR16G16Sfloat);
      //case latte::SQ_DATA_FORMAT::FMT_8_24:
      //case latte::SQ_DATA_FORMAT::FMT_8_24_FLOAT:
      case latte::SQ_DATA_FORMAT::FMT_24_8:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, BADFMT);
      //case latte::SQ_DATA_FORMAT::FMT_24_8_FLOAT:
      //case latte::SQ_DATA_FORMAT::FMT_10_11_11:
      case latte::SQ_DATA_FORMAT::FMT_10_11_11_FLOAT:
         decaf_abort("Encountered bit-reversed surface format");
         //return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eB10G11R11UfloatPack32);
      //case latte::SQ_DATA_FORMAT::FMT_11_11_10:
      case latte::SQ_DATA_FORMAT::FMT_11_11_10_FLOAT:
         decaf_abort("Encountered bit-reversed surface format");
         //return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eB10G11R11UfloatPack32);
      case latte::SQ_DATA_FORMAT::FMT_2_10_10_10:
         decaf_abort("Encountered bit-reversed surface format");
         //return pick(vk::Format::eA2B10G10R10UnormPack32, vk::Format::eA2B10G10R10SnormPack32, vk::Format::eA2B10G10R10UintPack32, vk::Format::eA2B10G10R10UnormPack32, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_8_8_8_8:
         return pick(vk::Format::eR8G8B8A8Unorm, vk::Format::eR8G8B8A8Snorm, vk::Format::eR8G8B8A8Uint, vk::Format::eR8G8B8A8Sint, vk::Format::eR8G8B8A8Srgb, vk::Format::eR8G8B8A8Sscaled);
      case latte::SQ_DATA_FORMAT::FMT_10_10_10_2:
         return pick(vk::Format::eA2B10G10R10UnormPack32, vk::Format::eA2B10G10R10SnormPack32, vk::Format::eA2B10G10R10UintPack32, vk::Format::eA2B10G10R10SintPack32, BADFMT, vk::Format::eA2B10G10R10SscaledPack32);
      //case latte::SQ_DATA_FORMAT::FMT_X24_8_32_FLOAT:
      case latte::SQ_DATA_FORMAT::FMT_32_32:
         return pick(BADFMT, BADFMT, vk::Format::eR32G32Uint, vk::Format::eR32G32Sint, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_32_32_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR32G32Sfloat);
      case latte::SQ_DATA_FORMAT::FMT_16_16_16_16:
         return pick(vk::Format::eR16G16B16A16Unorm, vk::Format::eR16G16B16A16Snorm, vk::Format::eR16G16B16A16Uint, vk::Format::eR16G16B16A16Sint, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_16_16_16_16_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR16G16B16A16Sfloat);
      case latte::SQ_DATA_FORMAT::FMT_32_32_32_32:
         return pick(BADFMT, BADFMT, vk::Format::eR32G32B32Uint, vk::Format::eR32G32B32Sint, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_32_32_32_32_FLOAT:
         return pick(BADFMT, BADFMT, BADFMT, BADFMT, BADFMT, vk::Format::eR32G32B32Sfloat);
      //case latte::SQ_DATA_FORMAT::FMT_1:
      //case latte::SQ_DATA_FORMAT::FMT_GB_GR:
      //case latte::SQ_DATA_FORMAT::FMT_BG_RG:
      //case latte::SQ_DATA_FORMAT::FMT_32_AS_8:
      //case latte::SQ_DATA_FORMAT::FMT_32_AS_8_8:
      //case latte::SQ_DATA_FORMAT::FMT_5_9_9_9_SHAREDEXP:
      //case latte::SQ_DATA_FORMAT::FMT_8_8_8:
      //case latte::SQ_DATA_FORMAT::FMT_16_16_16:
      //case latte::SQ_DATA_FORMAT::FMT_16_16_16_FLOAT:
      //case latte::SQ_DATA_FORMAT::FMT_32_32_32:
      //case latte::SQ_DATA_FORMAT::FMT_32_32_32_FLOAT:
      case latte::SQ_DATA_FORMAT::FMT_BC1:

         return pick(vk::Format::eBc1RgbaUnormBlock, BADFMT, BADFMT, BADFMT, vk::Format::eBc1RgbaSrgbBlock, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_BC2:
         return pick(vk::Format::eBc2UnormBlock, BADFMT, BADFMT, BADFMT, vk::Format::eBc2SrgbBlock, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_BC3:
         return pick(vk::Format::eBc3UnormBlock, BADFMT, BADFMT, BADFMT, vk::Format::eBc3SrgbBlock, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_BC4:
         return pick(vk::Format::eBc4UnormBlock, vk::Format::eBc4SnormBlock, BADFMT, BADFMT, BADFMT, BADFMT);
      case latte::SQ_DATA_FORMAT::FMT_BC5:
         return pick(vk::Format::eBc5UnormBlock, vk::Format::eBc5SnormBlock, BADFMT, BADFMT, BADFMT, BADFMT);
      //case latte::SQ_DATA_FORMAT::FMT_APC0:
      //case latte::SQ_DATA_FORMAT::FMT_APC1:
      //case latte::SQ_DATA_FORMAT::FMT_APC2:
      //case latte::SQ_DATA_FORMAT::FMT_APC3:
      //case latte::SQ_DATA_FORMAT::FMT_APC4:
      //case latte::SQ_DATA_FORMAT::FMT_APC5:
      //case latte::SQ_DATA_FORMAT::FMT_APC6:
      //case latte::SQ_DATA_FORMAT::FMT_APC7:
      //case latte::SQ_DATA_FORMAT::FMT_CTX1:
      default:
         decaf_abort("Unexpected color buffer format for vulkan");
      }
   }
}
Example #3
0
void
Driver::checkCurrentRenderPass()
{
   HashedDesc<RenderPassDesc> currentDesc = getRenderPassDesc();

   if (mCurrentRenderPass && mCurrentRenderPass->desc == currentDesc) {
      // Already active, nothing to do.
      return;
   }

   auto& foundRp = mRenderPasses[currentDesc.hash()];
   if (foundRp) {
      mCurrentRenderPass = foundRp;
      return;
   }

   foundRp = new RenderPassObject();
   foundRp->desc = currentDesc;

   std::vector<vk::AttachmentDescription> attachmentDescs;
   std::array<vk::AttachmentReference, latte::MaxRenderTargets> colorAttachmentRefs;
   vk::AttachmentReference depthAttachmentRef;

   for (auto i = 0u; i < latte::MaxRenderTargets; ++i) {
      auto &colorTarget = currentDesc->colorTargets[i];

      if (!colorTarget.isEnabled) {
         colorAttachmentRefs[i].attachment = VK_ATTACHMENT_UNUSED;
         colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;

         foundRp->colorAttachmentIndexes[i] = -1;
         continue;
      }

      auto dataFormat = latte::getColorBufferDataFormat(colorTarget.format, colorTarget.numberType);
      auto vulkanFormat = getSurfaceFormat(dataFormat.format, dataFormat.numFormat, dataFormat.formatComp, dataFormat.degamma, false);

      vk::AttachmentDescription colorAttachmentDesc;
      colorAttachmentDesc.format = vulkanFormat;
      colorAttachmentDesc.samples = vk::SampleCountFlagBits::e1;
      colorAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad;
      colorAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore;
      colorAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
      colorAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
      colorAttachmentDesc.initialLayout = vk::ImageLayout::eColorAttachmentOptimal;
      colorAttachmentDesc.finalLayout = vk::ImageLayout::eColorAttachmentOptimal;
      attachmentDescs.push_back(colorAttachmentDesc);
      auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1);

      colorAttachmentRefs[i].attachment = attachmentIndex;
      colorAttachmentRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;

      foundRp->colorAttachmentIndexes[i] = attachmentIndex;
   }

   if (!currentDesc->depthTarget.isEnabled) {
      depthAttachmentRef.attachment = VK_ATTACHMENT_UNUSED;
      depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

      foundRp->depthAttachmentIndex = -1;
   } else {
      auto depthTarget = currentDesc->depthTarget;

      auto dataFormat = latte::getDepthBufferDataFormat(depthTarget.format);
      auto vulkanFormat = getSurfaceFormat(dataFormat.format, dataFormat.numFormat, dataFormat.formatComp, dataFormat.degamma, true);

      vk::AttachmentDescription depthAttachmentDesc;
      depthAttachmentDesc.format = vulkanFormat;
      depthAttachmentDesc.samples = vk::SampleCountFlagBits::e1;
      depthAttachmentDesc.loadOp = vk::AttachmentLoadOp::eLoad;
      depthAttachmentDesc.storeOp = vk::AttachmentStoreOp::eStore;
      depthAttachmentDesc.stencilLoadOp = vk::AttachmentLoadOp::eLoad;
      depthAttachmentDesc.stencilStoreOp = vk::AttachmentStoreOp::eStore;
      depthAttachmentDesc.initialLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
      depthAttachmentDesc.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
      attachmentDescs.push_back(depthAttachmentDesc);
      auto attachmentIndex = static_cast<uint32_t>(attachmentDescs.size() - 1);

      depthAttachmentRef.attachment = attachmentIndex;
      depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

      foundRp->depthAttachmentIndex = attachmentIndex;
   }

   vk::SubpassDescription genericSubpass;
   genericSubpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
   genericSubpass.inputAttachmentCount = 0;
   genericSubpass.pInputAttachments = nullptr;
   genericSubpass.colorAttachmentCount = static_cast<uint32_t>(colorAttachmentRefs.size());
   genericSubpass.pColorAttachments = colorAttachmentRefs.data();
   genericSubpass.pResolveAttachments = 0;
   genericSubpass.pDepthStencilAttachment = &depthAttachmentRef;
   genericSubpass.preserveAttachmentCount = 0;
   genericSubpass.pPreserveAttachments = nullptr;

   vk::RenderPassCreateInfo renderPassDesc;
   renderPassDesc.attachmentCount = static_cast<uint32_t>(attachmentDescs.size());
   renderPassDesc.pAttachments = attachmentDescs.data();
   renderPassDesc.subpassCount = 1;
   renderPassDesc.pSubpasses = &genericSubpass;
   renderPassDesc.dependencyCount = 0;
   renderPassDesc.pDependencies = nullptr;
   auto renderPass = mDevice.createRenderPass(renderPassDesc);
   foundRp->renderPass = renderPass;

   mCurrentRenderPass = foundRp;
}
Example #4
0
bool GLDriver::checkActiveTextures()
{
   std::vector<uint8_t> untiledImage, untiledMipmap;
   gx2::GX2Surface surface;

   for (auto i = 0; i < latte::MaxTextures; ++i) {
      auto resourceOffset = (latte::SQ_PS_TEX_RESOURCE_0 + i) * 7;
      auto sq_tex_resource_word0 = getRegister<latte::SQ_TEX_RESOURCE_WORD0_N>(latte::Register::SQ_TEX_RESOURCE_WORD0_0 + 4 * resourceOffset);
      auto sq_tex_resource_word1 = getRegister<latte::SQ_TEX_RESOURCE_WORD1_N>(latte::Register::SQ_TEX_RESOURCE_WORD1_0 + 4 * resourceOffset);
      auto sq_tex_resource_word2 = getRegister<latte::SQ_TEX_RESOURCE_WORD2_N>(latte::Register::SQ_TEX_RESOURCE_WORD2_0 + 4 * resourceOffset);
      auto sq_tex_resource_word3 = getRegister<latte::SQ_TEX_RESOURCE_WORD3_N>(latte::Register::SQ_TEX_RESOURCE_WORD3_0 + 4 * resourceOffset);
      auto sq_tex_resource_word4 = getRegister<latte::SQ_TEX_RESOURCE_WORD4_N>(latte::Register::SQ_TEX_RESOURCE_WORD4_0 + 4 * resourceOffset);
      auto sq_tex_resource_word5 = getRegister<latte::SQ_TEX_RESOURCE_WORD5_N>(latte::Register::SQ_TEX_RESOURCE_WORD5_0 + 4 * resourceOffset);
      auto sq_tex_resource_word6 = getRegister<latte::SQ_TEX_RESOURCE_WORD6_N>(latte::Register::SQ_TEX_RESOURCE_WORD6_0 + 4 * resourceOffset);
      auto baseAddress = sq_tex_resource_word2.BASE_ADDRESS() << 8;

      if (!baseAddress) {
         continue;
      }

      if (baseAddress == mPixelTextureCache[i].baseAddress
       && sq_tex_resource_word0.value == mPixelTextureCache[i].word0
       && sq_tex_resource_word1.value == mPixelTextureCache[i].word1
       && sq_tex_resource_word2.value == mPixelTextureCache[i].word2
       && sq_tex_resource_word3.value == mPixelTextureCache[i].word3
       && sq_tex_resource_word4.value == mPixelTextureCache[i].word4
       && sq_tex_resource_word5.value == mPixelTextureCache[i].word5
       && sq_tex_resource_word6.value == mPixelTextureCache[i].word6) {
         continue;  // No change in sampler state
      }

      mPixelTextureCache[i].baseAddress = baseAddress;
      mPixelTextureCache[i].word0 = sq_tex_resource_word0.value;
      mPixelTextureCache[i].word1 = sq_tex_resource_word1.value;
      mPixelTextureCache[i].word2 = sq_tex_resource_word2.value;
      mPixelTextureCache[i].word3 = sq_tex_resource_word3.value;
      mPixelTextureCache[i].word4 = sq_tex_resource_word4.value;
      mPixelTextureCache[i].word5 = sq_tex_resource_word5.value;
      mPixelTextureCache[i].word6 = sq_tex_resource_word6.value;

      // Decode resource registers
      auto pitch = (sq_tex_resource_word0.PITCH() + 1) * 8;
      auto width = sq_tex_resource_word0.TEX_WIDTH() + 1;
      auto height = sq_tex_resource_word1.TEX_HEIGHT() + 1;
      auto depth = sq_tex_resource_word1.TEX_DEPTH() + 1;

      auto format = sq_tex_resource_word1.DATA_FORMAT();
      auto tileMode = sq_tex_resource_word0.TILE_MODE();
      auto numFormat = sq_tex_resource_word4.NUM_FORMAT_ALL();
      auto formatComp = sq_tex_resource_word4.FORMAT_COMP_X();
      auto degamma = sq_tex_resource_word4.FORCE_DEGAMMA();
      auto dim = sq_tex_resource_word0.DIM();

      auto buffer = getSurfaceBuffer(baseAddress, width, height, depth, dim, format, numFormat, formatComp, degamma, sq_tex_resource_word0.TILE_TYPE());

      if (buffer->dirtyAsTexture) {
         auto swizzle = sq_tex_resource_word2.SWIZZLE() << 8;

         // Rebuild a GX2Surface
         std::memset(&surface, 0, sizeof(gx2::GX2Surface));

         surface.dim = static_cast<gx2::GX2SurfaceDim>(dim);
         surface.width = width;
         surface.height = height;

         if (surface.dim == gx2::GX2SurfaceDim::TextureCube) {
            surface.depth = depth * 6;
         } else if (surface.dim == gx2::GX2SurfaceDim::Texture3D ||
            surface.dim == gx2::GX2SurfaceDim::Texture2DMSAAArray ||
            surface.dim == gx2::GX2SurfaceDim::Texture2DArray ||
            surface.dim == gx2::GX2SurfaceDim::Texture1DArray) {
            surface.depth = depth;
         } else {
            surface.depth = 1;
         }

         surface.mipLevels = 1;
         surface.format = getSurfaceFormat(format, numFormat, formatComp, degamma);

         surface.aa = gx2::GX2AAMode::Mode1X;
         surface.use = gx2::GX2SurfaceUse::Texture;

         if (sq_tex_resource_word0.TILE_TYPE()) {
            surface.use |= gx2::GX2SurfaceUse::DepthBuffer;
         }

         surface.tileMode = static_cast<gx2::GX2TileMode>(tileMode);
         surface.swizzle = swizzle;

         // Update the sizing information for the surface
         GX2CalcSurfaceSizeAndAlignment(&surface);

         // Align address
         baseAddress &= ~(surface.alignment - 1);

         surface.image = make_virtual_ptr<uint8_t>(baseAddress);
         surface.mipmaps = nullptr;

         // Calculate a new memory CRC
         uint64_t newHash[2] = { 0 };
         MurmurHash3_x64_128(surface.image, surface.imageSize, 0, newHash);

         // If the CPU memory has changed, we should re-upload this.  This hashing is
         //  also means that if the application temporarily uses one of its buffers as
         //  a color buffer, we are able to accurately handle this.  Providing they are
         //  not updating the memory at the same time.
         if (newHash[0] != buffer->cpuMemHash[0] || newHash[1] != buffer->cpuMemHash[1]) {
            buffer->cpuMemHash[0] = newHash[0];
            buffer->cpuMemHash[1] = newHash[1];

            // Untile
            gx2::internal::convertTiling(&surface, untiledImage, untiledMipmap);

            // Create texture
            auto compressed = isCompressedFormat(format);
            auto target = getTextureTarget(dim);
            auto textureDataType = gl::GL_INVALID_ENUM;
            auto textureFormat = getTextureFormat(format);
            auto size = untiledImage.size();

            if (compressed) {
               textureDataType = getCompressedTextureDataType(format, degamma);
            } else {
               textureDataType = getTextureDataType(format, formatComp);
            }

            if (textureDataType == gl::GL_INVALID_ENUM || textureFormat == gl::GL_INVALID_ENUM) {
               decaf_abort(fmt::format("Texture with unsupported format {}", surface.format.value()));
            }

            switch (dim) {
            case latte::SQ_TEX_DIM_1D:
               if (compressed) {
                  gl::glCompressedTextureSubImage1D(buffer->object,
                                                    0, /* level */
                                                    0, /* xoffset */
                                                    width,
                                                    textureDataType,
                                                    gsl::narrow_cast<gl::GLsizei>(size),
                                                    untiledImage.data());
               } else {
                  gl::glTextureSubImage1D(buffer->object,
                                          0, /* level */
                                          0, /* xoffset */
                                          width,
                                          textureFormat,
                                          textureDataType,
                                          untiledImage.data());
               }
               break;
            case latte::SQ_TEX_DIM_2D:
               if (compressed) {
                  gl::glCompressedTextureSubImage2D(buffer->object,
                                                    0, /* level */
                                                    0, 0, /* xoffset, yoffset */
                                                    width,
                                                    height,
                                                    textureDataType,
                                                    gsl::narrow_cast<gl::GLsizei>(size),
                                                    untiledImage.data());
               } else {
                  gl::glTextureSubImage2D(buffer->object,
                                          0, /* level */
                                          0, 0, /* xoffset, yoffset */
                                          width, height,
                                          textureFormat,
                                          textureDataType,
                                          untiledImage.data());
               }
               break;
            case latte::SQ_TEX_DIM_3D:
               if (compressed) {
                  gl::glCompressedTextureSubImage3D(buffer->object,
                                                    0, /* level */
                                                    0, 0, 0, /* xoffset, yoffset, zoffset */
                                                    width, height, depth,
                                                    textureDataType,
                                                    gsl::narrow_cast<gl::GLsizei>(size),
                                                    untiledImage.data());
               } else {
                  gl::glTextureSubImage3D(buffer->object,
                                          0, /* level */
                                          0, 0, 0, /* xoffset, yoffset, zoffset */
                                          width, height, depth,
                                          textureFormat,
                                          textureDataType,
                                          untiledImage.data());
               }
               break;
            case latte::SQ_TEX_DIM_CUBEMAP:
               decaf_check(surface.depth == 6);
            case latte::SQ_TEX_DIM_2D_ARRAY:
               if (compressed) {
                  gl::glCompressedTextureSubImage3D(buffer->object,
                                                    0, /* level */
                                                    0, 0, 0, /* xoffset, yoffset, zoffset */
                                                    width, height, surface.depth,
                                                    textureDataType,
                                                    gsl::narrow_cast<gl::GLsizei>(size),
                                                    untiledImage.data());
               } else {
                  gl::glTextureSubImage3D(buffer->object,
                                          0, /* level */
                                          0, 0, 0, /* xoffset, yoffset, zoffset */
                                          width, height, surface.depth,
                                          textureFormat,
                                          textureDataType,
                                          untiledImage.data());
               }
               break;
            default:
               decaf_abort(fmt::format("Unsupported texture dim: {}", sq_tex_resource_word0.DIM()));
            }
         }

         buffer->dirtyAsTexture = false;
         buffer->state = SurfaceUseState::CpuWritten;
      }

      // Setup texture swizzle
      auto dst_sel_x = getTextureSwizzle(sq_tex_resource_word4.DST_SEL_X());
      auto dst_sel_y = getTextureSwizzle(sq_tex_resource_word4.DST_SEL_Y());
      auto dst_sel_z = getTextureSwizzle(sq_tex_resource_word4.DST_SEL_Z());
      auto dst_sel_w = getTextureSwizzle(sq_tex_resource_word4.DST_SEL_W());

      gl::GLint textureSwizzle[] = {
         static_cast<gl::GLint>(dst_sel_x),
         static_cast<gl::GLint>(dst_sel_y),
         static_cast<gl::GLint>(dst_sel_z),
         static_cast<gl::GLint>(dst_sel_w),
      };

      gl::glTextureParameteriv(buffer->object, gl::GL_TEXTURE_SWIZZLE_RGBA, textureSwizzle);
      gl::glBindTextureUnit(i, buffer->object);
   }

   return true;
}