Skip to content

Doesn't currently deswizzle uncompressed PS3 textures #4

@craftablescience

Description

@craftablescience

PS3 textures which are uncompressed use Morton swizzling for quick reading from the GPU. This makes them look like garbage when read expecting a normal texture. Since it's a lossless transformation it's possible to undo it to get the proper expected texture data.

See the logic in vtfpp under MIT license here. This routine is used in 4 tools I've made (VPKEdit, MareTF, VTF Thumbnailer, QVTF++) to compile/decompile PS3 VTFs and I haven't had issue with it yet.

[[nodiscard]] constexpr uint32_t log2ceil(uint32_t value) {
	// https://github.com/lastland/Tricks-Museum/blob/master/FastLog2/FastLog2.c
	return ((std::bit_cast<uint32_t>(static_cast<float>(value)) >> 23) & 0xff) - 127;
}

template<bool ExistingDataIsSwizzled>
constexpr void swizzleImageDataForPS3(std::span<std::byte> inputData, std::span<std::byte> outputData, ImageFormat format, uint16_t width, uint16_t height, uint16_t slice) {
	width *= ImageFormatDetails::bpp(format) / 32;
	const auto zIndex = [
		widthL2 = static_cast<int>(log2ceil(width)),
		heightL2 = static_cast<int>(log2ceil(height)),
		sliceL2 = static_cast<int>(log2ceil(slice))
	](uint32_t x, uint32_t y, uint32_t z) {
		auto widthL2m = widthL2;
		auto heightL2m = heightL2;
		auto sliceL2m = sliceL2;
		uint32_t offset = 0;
		uint32_t shiftCount = 0;
		do {
			if (sliceL2m --> 0) {
				offset |= (z & 1) << shiftCount++;
				z >>= 1;
			}
			if (heightL2m --> 0) {
				offset |= (y & 1) << shiftCount++;
				y >>= 1;
			}
			if (widthL2m --> 0) {
				offset |= (x & 1) << shiftCount++;
				x >>= 1;
			}
		} while (x | y | z);
		return offset;
	};

	const auto* inputPtr = reinterpret_cast<const uint32_t*>(inputData.data());
	auto* outputPtr = reinterpret_cast<uint32_t*>(outputData.data());
	for (uint16_t x = 0; x < width; x++) {
		for (uint16_t y = 0; y < height; y++) {
			for (uint16_t z = 0; z < slice; z++) {
				if constexpr (ExistingDataIsSwizzled) {
					*outputPtr++ = reinterpret_cast<const uint32_t*>(inputData.data())[zIndex(x, y, z)];
				} else {
					reinterpret_cast<uint32_t*>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
				}
			}
		}
	}
}

template<bool ConvertingFromSource>
void swapImageDataEndianForConsole(std::span<std::byte> imageData, ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t sliceCount, VTF::Platform platform) {

	// ... x360 dxt endian stuff here ... //

	if ((platform == VTF::PLATFORM_PS3_ORANGEBOX || platform == VTF::PLATFORM_PS3_PORTAL2) && !ImageFormatDetails::compressed(format) && ImageFormatDetails::bpp(format) % 32 == 0) {
		std::vector<std::byte> out(imageData.size());
		for(int mip = mipCount - 1; mip >= 0; mip--) {
			const auto mipWidth = ImageDimensions::getMipDim(mip, width);
			const auto mipHeight = ImageDimensions::getMipDim(mip, height);
			for (int frame = 0; frame < frameCount; frame++) {
				for (int face = 0; face < faceCount; face++) {
					if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height)) {
						std::span imageDataSpan{imageData.data() + offset, length * sliceCount};
						std::span outSpan{out.data() + offset, length * sliceCount};
						::swizzleImageDataForPS3<ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, sliceCount);
					}
				}
			}
		}
		std::memcpy(imageData.data(), out.data(), out.size());
	}
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions