AKA: I was dumb and spent a whole week writing 3 separate implementations of texture decompression, trying 50 permutations of vkCmdPipelineBarriers before realizing that I forgot to account for vkCreateImageView

vt_handle_vkCreateImageView

In addition to intercepting vkCreateImage, vortek must also intercept all usages of vkCreateImageView in order to present specific views of a texture that may be rendered with the BCn format. Vortek handles this in a pretty straightforward fashion:


void vt_handle_vkCreateImageView(VtContext* ctx) {
    char* serialized_payload = ctx->current_command_data_buffer;

    // Usual decompression loop, which includes desesrializing the VkImageViewCreateInfo* createInfo
    ...

    // 2. Check for Managed Image and Correct Format
    if (ctx->texture_decoder != NULL && isCompressedFormat(createInfo.format)) {
        createInfo.format = VK_FORMAT_B8G8R8A8_UNORM; // 0x2c
        createInfo.subresourceRange.levelCount = 1; // Force mip-level to be 1
    }

    // 3. Native Vulkan Call
    VkImageView imageView;
    VkResult result = vkCreateImageView(device, &createInfo, NULL, &imageView);

    // 4. Write Response
    RingBuffer_write(ctx->pResponseBuffer, &result, sizeof(VkResult));
    RingBuffer_write(ctx->pResponseBuffer, &imageView, sizeof(VkImageView));
}

For now, Vortek doesn’t support mip-chains (though in theory, this shouldn’t be hard to support), and it may lead to strange low-res texture rendering as it will always take the lowest resolution mip from dxvk (which submits its mipchains from highest to lowest resolution)

vt_handle_vkGetPhysicalDeviceImageFormatProperties

Additionally, we must advertise support for BCn textures by responding to format property queries. This is typically done via vkGetPhysicalDeviceImageFormatProperties, which vortek handles by returning the following VkImageFormatProperties:

VkResult getCompressedImageFormatProperties(VkFormat format, VkImageFormatProperties* pImageFormatProperties) {
    if (format >= VK_FORMAT_BC1_RGB_UNORM_BLOCK && format <= VK_FORMAT_BC5_SNORM_BLOCK) {
        
        // *param_2 = 0x400000004000;
        pImageFormatProperties->maxExtent.width = 16384;      // offset: +0x00, value: 0x4000
        pImageFormatProperties->maxExtent.height = 16384;     // offset: +0x04, value: 0x4000

        // param_2[1] = 0xf00000001;
        pImageFormatProperties->maxExtent.depth = 1;          // offset: +0x08, value: 0x1
        pImageFormatProperties->maxMipLevels = 15;            // offset: +0x0C, value: 0xf

        // param_2[2] = 0x100000800;
        pImageFormatProperties->maxArrayLayers = 2048;        // offset: +0x10, value: 0x800
        pImageFormatProperties->sampleCounts = VK_SAMPLE_COUNT_1_BIT; // offset: +0x14, VK_SAMPLE_COUNT_1_BIT = 1

        // param_2[3] = 0x80000000;
        pImageFormatProperties->maxResourceSize = 2147483648; // offset: +0x18, value: 0x80000000 (2GB)

        return VK_SUCCESS; // 0
    }

    return VK_ERROR_FORMAT_NOT_SUPPORTED; // -11 (0xfffffff5)
}

void vt_handle_vkGetPhysicalDeviceImageFormatProperties(VortekContext* ctx) {
    // 1. Argument Deserialization
    ...

    VkImageFormatProperties imageFormatProperties = {0};
    
    // `(*_DAT_0013c6b8)` is the function pointer to vkGetPhysicalDeviceImageFormatProperties.
    VkResult result = vkGetPhysicalDeviceImageFormatProperties(
        physicalDeviceObject,
        format,
        type,
        tiling,
        usage,
        flags,
        &imageFormatProperties
    );

    // If the driver doesn't support the format, check if we can decode it ourselves.
    if (result == VK_ERROR_FORMAT_NOT_SUPPORTED) { // result == -11
        if (isCompressedFormat(format)) {
            // Provide our own dummy properties and report success.
            result = getCompressedImageFormatProperties(format, &imageFormatProperties);
        }
    }
    
    RingBuffer_write(ctx->pResponseBuffer, &result, sizeof(VkResult));
    RingBuffer_write(ctx->pResponseBuffer, &imageFormatProperties, sizeof(VkImageFormatProperties));
}