void copyTexture(const ThreadDispatcher::message& msg) { const CreateTextureTask* task = (const CreateTextureTask*)msg.cbData; MemoryTexture* src = task->src.get(); NativeTexture* dest = task->dest.get(); bool done = false; if (isCompressedFormat(src->getFormat())) { dest->init(src->lock(), false); done = true; } else { Rect textureRect(0, 0, src->getWidth(), src->getHeight()); if (dest->getHandle() == 0) dest->init(textureRect.getWidth(), textureRect.getHeight(), src->getFormat()); dest->updateRegion(0, 0, src->lock()); } task->ready(); }
void GFXGLCubemap::fillCubeTextures(GFXTexHandle* faces) { AssertFatal( faces, ""); AssertFatal( faces[0]->mMipLevels > 0, ""); PRESERVE_CUBEMAP_TEXTURE(); glBindTexture(GL_TEXTURE_CUBE_MAP, mCubemap); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, faces[0]->mMipLevels - 1 ); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); U32 reqWidth = faces[0]->getWidth(); U32 reqHeight = faces[0]->getHeight(); GFXFormat regFaceFormat = faces[0]->getFormat(); const bool isCompressed = isCompressedFormat(regFaceFormat); mWidth = reqWidth; mHeight = reqHeight; mFaceFormat = regFaceFormat; mMipLevels = getMax( (U32)1, faces[0]->mMipLevels); AssertFatal(reqWidth == reqHeight, "GFXGLCubemap::fillCubeTextures - Width and height must be equal!"); for(U32 i = 0; i < 6; i++) { AssertFatal(faces[i], avar("GFXGLCubemap::fillCubeFaces - texture %i is NULL!", i)); AssertFatal((faces[i]->getWidth() == reqWidth) && (faces[i]->getHeight() == reqHeight), "GFXGLCubemap::fillCubeFaces - All textures must have identical dimensions!"); AssertFatal(faces[i]->getFormat() == regFaceFormat, "GFXGLCubemap::fillCubeFaces - All textures must have identical formats!"); mTextures[i] = faces[i]; GFXFormat faceFormat = faces[i]->getFormat(); GFXGLTextureObject* glTex = static_cast<GFXGLTextureObject*>(faces[i].getPointer()); if( isCompressed ) { for( U32 mip = 0; mip < mMipLevels; ++mip ) { const U32 mipWidth = getMax( U32(1), faces[i]->getWidth() >> mip ); const U32 mipHeight = getMax( U32(1), faces[i]->getHeight() >> mip ); const U32 mipDataSize = getCompressedSurfaceSize( mFaceFormat, mWidth, mHeight, mip ); U8* buf = glTex->getTextureData( mip ); glCompressedTexImage2D(faceList[i], mip, GFXGLTextureInternalFormat[mFaceFormat], mipWidth, mipHeight, 0, mipDataSize, buf); delete[] buf; } } else { U8* buf = glTex->getTextureData(); glTexImage2D(faceList[i], 0, GFXGLTextureInternalFormat[faceFormat], mWidth, mHeight, 0, GFXGLTextureFormat[faceFormat], GFXGLTextureType[faceFormat], buf); delete[] buf; } }
void ResAtlas::loadAtlas(CreateResourceContext &context) { //string path = context.walker.getCurrentFolder(); pugi::xml_node node = context.walker.getNode(); pugi::xml_node meta = context.walker.getMeta(); int w = node.attribute("width").as_int(defaultAtlasWidth); int h = node.attribute("height").as_int(defaultAtlasHeight); const char *format = node.attribute("format").as_string("8888"); atlas_data ad; TextureFormat tf = string2TextureFormat(format); pugi::xml_node meta_image = meta.child("atlas"); bool compressed = false; while(meta_image) { const char *file = meta_image.attribute("file").value(); int w = meta_image.attribute("w").as_int(); int h = meta_image.attribute("h").as_int(); const char *file_format = meta_image.attribute("format").as_string(0); TextureFormat ffmt = TF_UNDEFINED; if (file_format) { ffmt = string2TextureFormat(file_format); compressed = isCompressedFormat(ffmt); } std::string alpha_file = meta_image.attribute("alpha").as_string(""); if (!alpha_file.empty()) { alpha_file = *context.prebuilt_folder + alpha_file; } addAtlas(tf, *context.prebuilt_folder + file, alpha_file, w, h); meta_image = meta_image.next_sibling("atlas"); context.walker.nextMeta(); } // std::vector<ResAnim*> anims; while (true) { XmlWalker walker = context.walker.next(); if (walker.empty()) break; pugi::xml_node child_node = walker.getNode(); pugi::xml_node meta_frames = walker.getMeta(); const char *name = child_node.name(); if (!strcmp(name, "image")) { std::string id = child_node.attribute("id").value(); std::string file = child_node.attribute("file").value(); if (file.empty()) { ResAnim *ra = new ResAnim(this); ra->init(0, 0, 0, walker.getScaleFactor()); init_resAnim(ra, file, child_node); ra->setParent(this); context.resources->add(ra); continue; } if (meta) { OX_ASSERT(meta_frames && "Did you recreate atlasses?"); } MemoryTexture mt; ImageData im; int columns = 0; int rows = 0; int frame_width = 0; int frame_height = 0; float frame_scale = 1.0f; bool loaded = false; if (meta_frames || meta) { const char *frame_size = meta_frames.attribute("fs").value(); //int w = 0; //int h = 0; sscanf(frame_size, "%d,%d,%d,%d,%f", &columns, &rows, &frame_width, &frame_height, &frame_scale); loaded = true; //frame_scale = 0. //frame_scale /= walker.getScaleFactor();//todo! fix //im.w = w; //im.h = h; } else { file::buffer bf; file::read(walker.getPath("file").c_str(), bf); mt.init(bf, Renderer::getPremultipliedAlphaRender(), tf); im = mt.lock(); if (im.w) { rows = child_node.attribute("rows").as_int(); frame_width = child_node.attribute("frame_width").as_int(); columns = child_node.attribute("cols").as_int(); frame_height = child_node.attribute("frame_height").as_int(); if (!rows) rows = 1; if (!columns) columns = 1; if (frame_width) columns = im.w / frame_width; else frame_width = im.w / columns; if (frame_height) rows = im.h / frame_height; else frame_height = im.h / rows; } } if (columns) { animationFrames frames; int frames_count = rows * columns; frames.reserve(frames_count); ResAnim *ra = new ResAnim(this); if (meta) { OX_ASSERT(meta_frames); /* if (string(meta_frames.attribute("debug_image").as_string()) == "backleft.png") { } */ char *frames_data = (char*)meta_frames.first_child().value(); const char *begin = frames_data; while(*frames_data) { if (*frames_data == ';') { *frames_data = 0; int id = 0; int x = 0; int y = 0; int bbox_x = 0; int bbox_y = 0; int bbox_w = 0; int bbox_h = 0; sscanf(begin, "%d,%d,%d,%d,%d,%d,%d", &id, &x, &y, &bbox_x, &bbox_y, &bbox_w, &bbox_h); begin = frames_data + 1; spNativeTexture &texture = _atlasses[id].base; spNativeTexture &alpha = _atlasses[id].alpha; float iw = 1.0f / texture->getWidth(); float ih = 1.0f / texture->getHeight(); RectF srcRect(x * iw, y * ih, bbox_w * iw, bbox_h * ih); float fs = frame_scale; RectF destRect( Vector2((float)bbox_x, (float)bbox_y) * fs, Vector2((float)bbox_w, (float)bbox_h) * fs ); AnimationFrame frame; Diffuse df; df.base = texture; df.alpha = alpha; //compressed data could not be premultiplied if (Renderer::getPremultipliedAlphaRender()) df.premultiplied = !compressed; else df.premultiplied = true;//render should think that it is already premultiplied and don't worry about alpha frame.init(ra, df, srcRect, destRect, Vector2((float)frame_width, (float)frame_height)); frames.push_back(frame); if((int)frames.size() >= frames_count) break; } ++frames_data; } } else { anims.push_back(ra); for (int y = 0; y < rows; ++y) { for (int x = 0; x < columns; ++x) { Rect src; src.pos = Point(x * frame_width, y * frame_height); src.size = Point(frame_width, frame_height); ImageData srcImage = im.getRect(src); Rect dest(0,0,0,0); if (!ad.texture) { std::string atlas_id = getName(); next_atlas(w, h, tf, ad, atlas_id.c_str()); } bool s = ad.atlas.add(&ad.mt, srcImage, dest); if (s == false) { apply_atlas(ad); next_atlas(w, h, tf, ad, walker.getCurrentFolder().c_str()); s = ad.atlas.add(&ad.mt, srcImage, dest); OX_ASSERT(s); } /* float iw = 1.0f / ad.mt.getWidth(); float ih = 1.0f / ad.mt.getHeight(); */ float iw = 1.0f; float ih = 1.0f; RectF srcRect(dest.pos.x * iw, dest.pos.y * ih, dest.size.x * iw, dest.size.y * ih); Vector2 sizeScaled = Vector2((float)dest.size.x, (float)dest.size.y) * walker.getScaleFactor(); RectF destRect(Vector2(0, 0), sizeScaled); AnimationFrame frame; Diffuse df; df.base = ad.texture; df.premultiplied = true;//!Renderer::getPremultipliedAlphaRender(); frame.init(ra, df, srcRect, destRect, Vector2((float)frame_width, (float)frame_height) * walker.getScaleFactor()); frames.push_back(frame); } } } init_resAnim(ra, file, child_node); ra->init(frames, columns, walker.getScaleFactor()); ra->setParent(this); context.resources->add(ra); } } } apply_atlas(ad); for (std::vector<ResAnim*>::iterator i = anims.begin(); i != anims.end(); ++i) { ResAnim *rs = *i; int num = rs->getTotalFrames(); for (int n = 0; n < num; ++n) { AnimationFrame &frame = const_cast<AnimationFrame&>(rs->getFrame(n)); float iw = 1.0f / frame.getDiffuse().base->getWidth(); float ih = 1.0f / frame.getDiffuse().base->getHeight(); RectF rect = frame.getSrcRect(); rect.pos.x *= iw; rect.pos.y *= ih; rect.size.x *= iw; rect.size.y *= ih; frame.setSrcRect(rect); } } }
GLES2RendererTexture2D::GLES2RendererTexture2D(const RendererTextureDesc &desc) : RendererTexture2D(desc) { m_textureid = 0; m_glformat = 0; m_glinternalformat = 0; m_gltype = 0; m_numLevels = 0; m_data = 0; m_glformat = getGLPixelFormat(desc.format); m_glinternalformat = getGLPixelInternalFormat(desc.format); m_gltype = getGLPixelType(desc.format); glGenTextures(1, &m_textureid); RENDERER_ASSERT(m_textureid, "Failed to create OpenGL Texture."); if(m_textureid) { m_width = desc.width; m_height = desc.height; m_numLevels = desc.numLevels; m_data = new PxU8*[m_numLevels]; memset(m_data, 0, sizeof(PxU8*)*m_numLevels); glBindTexture(GL_TEXTURE_2D, m_textureid); if(isCompressedFormat(desc.format)) { for(PxU32 i=0; i<desc.numLevels; i++) { PxU32 w = getLevelDimension(m_width, i); PxU32 h = getLevelDimension(m_height, i); PxU32 levelSize = computeImageByteSize(w, h, 1, desc.format); m_data[i] = new PxU8[levelSize]; memset(m_data[i], 0, levelSize); glCompressedTexImage2D(GL_TEXTURE_2D, (GLint)i, m_glinternalformat, w, h, 0, levelSize, m_data[i]); } } else { RENDERER_ASSERT(m_glformat, "Unknown OpenGL Format!"); for(PxU32 i=0; i<desc.numLevels; i++) { PxU32 w = getLevelDimension(m_width, i); PxU32 h = getLevelDimension(m_height, i); PxU32 levelSize = computeImageByteSize(w, h, 1, desc.format); m_data[i] = new PxU8[levelSize]; memset(m_data[i], 0, levelSize); glTexImage2D(GL_TEXTURE_2D, (GLint)i, m_glinternalformat, w, h, 0, m_glformat, m_gltype, m_data[i]); } } // set filtering mode... glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, getGLTextureFilter(desc.filter, m_numLevels > 1)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, getGLTextureFilter(desc.filter, false)); if(desc.filter == FILTER_ANISOTROPIC) { GLfloat maxAnisotropy = 1.0f; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); } // set addressing modes... glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, getGLTextureAddressing(desc.addressingU)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, getGLTextureAddressing(desc.addressingV)); glBindTexture(GL_TEXTURE_2D, 0); } }
void Mem2Native::updateTexture() { int SIZE = _size; if (!SIZE) SIZE = RECT_SIZE; MemoryTexture* src = _opt->src.get(); NativeTexture* dest = _opt->dest.get(); Point& prev = _prev; bool done = false; if (isCompressedFormat(src->getFormat())) { dest->init(src->lock(), false); done = true; } else { Rect textureRect(0, 0, src->getWidth(), src->getHeight()); if (dest->getHandle() == 0) dest->init(textureRect.getWidth(), textureRect.getHeight(), src->getFormat()); Rect srcRect(prev.x, prev.y, std::min(SIZE, textureRect.getWidth()), std::min(SIZE, textureRect.getHeight())); srcRect.clip(textureRect); ImageData srcim = src->lock(&srcRect); ImageData destim; if (srcRect != textureRect) { int pitch = srcRect.getWidth() * getBytesPerPixel(dest->getFormat()); _buffer.resize(srcRect.getHeight() * pitch); destim = ImageData( srcRect.getWidth(), srcRect.getHeight(), pitch, dest->getFormat(), &_buffer[0] ); operations::copy(srcim, destim); } else { destim = srcim; } dest->updateRegion(srcRect.pos.x, srcRect.pos.y, destim); prev.x += SIZE; if (prev.x >= textureRect.getWidth()) { prev.x = 0; prev.y += SIZE; } if (prev.y >= textureRect.getBottom()) { _buffer.clear(); prev = Point(0, 0); done = true; } } if (done) { textureDone(); } }
TexturesInspector::TexturesInspector(const Vector2& size) { setSize(size); spSlidingActor slide = new SlidingActor; slide->setSize(size); addChild(slide); float offsetY = 0; std::vector<spNativeTexture> textures = NativeTexture::getCreatedTextures(); spTextField text = initActor(new TextField, arg_color = Color::White, arg_pos = Vector2(1, 1), arg_w = itemSize.x * 3.0f, arg_h = 30.0f, arg_vAlign = TextStyle::VALIGN_TOP, arg_hAlign = TextStyle::HALIGN_LEFT, arg_multiline = true, arg_attachTo = slide ); offsetY += text->getTextRect().getBottom() + 5; spActor content = new Actor; content->setX(2); int numX = (int)(size.x / itemSize.x); int n = 0; int mem = 0; for (std::vector<spNativeTexture>::reverse_iterator i = textures.rbegin(); i != textures.rend(); ++i) { spNativeTexture t = *i; TextureLine* line = new TextureLine(t); float x = (n % numX) * (itemSize.x + 5.0f); float y = (n / numX) * (itemSize.y + 5.0f); line->setX(x); line->setY(y + offsetY); content->addChild(line); ++n; if (t->getHandle()) { TextureFormat fmt = t->getFormat(); int ram = t->getWidth() * t->getHeight(); if (isCompressedFormat(fmt)) { switch (fmt) { case TF_PVRTC_4RGBA: ram /= 2; break; case TF_ETC1: ram /= 2; break; default: break; } } else ram *= getBytesPerPixel(fmt); mem += ram; } } char txt[255]; safe_sprintf(txt, "created textures: %d, vram: %d kb", textures.size(), mem / 1024); text->setText(txt); if (numX > n) numX = n; content->setSize( (itemSize.x + 5.0f) * numX, (itemSize.y + 5.0f) * (n + numX - 1.0f) / numX + offsetY); setWidth(content->getWidth()); slide->setWidth(content->getWidth()); //slide->setSize() slide->setContent(content); }
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; }