419 lines
16 KiB
C
419 lines
16 KiB
C
#include "vulkan.h"
|
|
|
|
#include <kinc/graphics5/texture.h>
|
|
#include <kinc/image.h>
|
|
#include <kinc/log.h>
|
|
|
|
bool use_staging_buffer = false;
|
|
|
|
bool memory_type_from_properties(uint32_t typeBits, VkFlags requirements_mask, uint32_t *typeIndex);
|
|
void set_image_layout(VkImage image, VkImageAspectFlags aspectMask, VkImageLayout old_image_layout, VkImageLayout new_image_layout);
|
|
|
|
static void prepare_texture_image(uint8_t *tex_colors, uint32_t tex_width, uint32_t tex_height, struct texture_object *tex_obj, VkImageTiling tiling,
|
|
VkImageUsageFlags usage, VkFlags required_props, VkDeviceSize *deviceSize, VkFormat tex_format) {
|
|
VkResult err;
|
|
bool pass;
|
|
|
|
tex_obj->tex_width = tex_width;
|
|
tex_obj->tex_height = tex_height;
|
|
|
|
VkImageCreateInfo image_create_info = {0};
|
|
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_create_info.pNext = NULL;
|
|
image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_create_info.format = tex_format;
|
|
image_create_info.extent.width = tex_width;
|
|
image_create_info.extent.height = tex_height;
|
|
image_create_info.extent.depth = 1;
|
|
image_create_info.mipLevels = 1;
|
|
image_create_info.arrayLayers = 1;
|
|
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
image_create_info.tiling = tiling;
|
|
image_create_info.usage = usage;
|
|
image_create_info.flags = 0;
|
|
image_create_info.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;
|
|
|
|
VkMemoryAllocateInfo mem_alloc = {0};
|
|
mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
mem_alloc.pNext = NULL;
|
|
mem_alloc.allocationSize = 0;
|
|
mem_alloc.memoryTypeIndex = 0;
|
|
|
|
VkMemoryRequirements mem_reqs;
|
|
|
|
err = vkCreateImage(vk_ctx.device, &image_create_info, NULL, &tex_obj->image);
|
|
assert(!err);
|
|
|
|
vkGetImageMemoryRequirements(vk_ctx.device, tex_obj->image, &mem_reqs);
|
|
|
|
*deviceSize = mem_alloc.allocationSize = mem_reqs.size;
|
|
pass = memory_type_from_properties(mem_reqs.memoryTypeBits, required_props, &mem_alloc.memoryTypeIndex);
|
|
assert(pass);
|
|
|
|
// allocate memory
|
|
err = vkAllocateMemory(vk_ctx.device, &mem_alloc, NULL, &tex_obj->mem);
|
|
assert(!err);
|
|
|
|
// bind memory
|
|
err = vkBindImageMemory(vk_ctx.device, tex_obj->image, tex_obj->mem, 0);
|
|
assert(!err);
|
|
|
|
if (required_props & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT && tex_colors != NULL) {
|
|
VkImageSubresource subres = {0};
|
|
subres.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
subres.mipLevel = 0;
|
|
subres.arrayLayer = 0;
|
|
|
|
VkSubresourceLayout layout;
|
|
uint8_t *data;
|
|
|
|
vkGetImageSubresourceLayout(vk_ctx.device, tex_obj->image, &subres, &layout);
|
|
|
|
err = vkMapMemory(vk_ctx.device, tex_obj->mem, 0, mem_alloc.allocationSize, 0, (void **)&data);
|
|
assert(!err);
|
|
|
|
if (tex_format == VK_FORMAT_R8_UNORM) {
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data[y * layout.rowPitch + x] = tex_colors[y * tex_width + x];
|
|
}
|
|
}
|
|
}
|
|
else if (tex_format == VK_FORMAT_R32_SFLOAT) {
|
|
uint32_t *data32 = (uint32_t *)data;
|
|
uint32_t *tex_colors32 = (uint32_t *)tex_colors;
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data32[y * (layout.rowPitch / 4) + x] = tex_colors32[y * tex_width + x];
|
|
}
|
|
}
|
|
}
|
|
else if (tex_format == VK_FORMAT_R16_SFLOAT) {
|
|
uint16_t *data16 = (uint16_t *)data;
|
|
uint16_t *tex_colors16 = (uint16_t *)tex_colors;
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data16[y * (layout.rowPitch / 4) + x] = tex_colors16[y * tex_width + x];
|
|
}
|
|
}
|
|
}
|
|
else if (tex_format == VK_FORMAT_R32G32B32A32_SFLOAT) {
|
|
uint32_t *data32 = (uint32_t *)data;
|
|
uint32_t *tex_colors32 = (uint32_t *)tex_colors;
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data32[y * (layout.rowPitch / 4) + x * 4 + 0] = tex_colors32[y * tex_width * 4 + x * 4 + 0];
|
|
data32[y * (layout.rowPitch / 4) + x * 4 + 1] = tex_colors32[y * tex_width * 4 + x * 4 + 1];
|
|
data32[y * (layout.rowPitch / 4) + x * 4 + 2] = tex_colors32[y * tex_width * 4 + x * 4 + 2];
|
|
data32[y * (layout.rowPitch / 4) + x * 4 + 3] = tex_colors32[y * tex_width * 4 + x * 4 + 3];
|
|
}
|
|
}
|
|
}
|
|
else if (tex_format == VK_FORMAT_R16G16B16A16_SFLOAT) {
|
|
uint16_t *data16 = (uint16_t *)data;
|
|
uint16_t *tex_colors16 = (uint16_t *)tex_colors;
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data16[y * (layout.rowPitch / 4) + x * 4 + 0] = tex_colors16[y * tex_width * 4 + x * 4 + 0];
|
|
data16[y * (layout.rowPitch / 4) + x * 4 + 1] = tex_colors16[y * tex_width * 4 + x * 4 + 1];
|
|
data16[y * (layout.rowPitch / 4) + x * 4 + 2] = tex_colors16[y * tex_width * 4 + x * 4 + 2];
|
|
data16[y * (layout.rowPitch / 4) + x * 4 + 3] = tex_colors16[y * tex_width * 4 + x * 4 + 3];
|
|
}
|
|
}
|
|
}
|
|
else if (tex_format == VK_FORMAT_B8G8R8A8_UNORM) {
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
// uint32_t *row = (uint32_t *)((char *)data + layout.rowPitch * y);
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data[y * layout.rowPitch + x * 4 + 0] = tex_colors[y * tex_width * 4 + x * 4 + 2];
|
|
data[y * layout.rowPitch + x * 4 + 1] = tex_colors[y * tex_width * 4 + x * 4 + 1];
|
|
data[y * layout.rowPitch + x * 4 + 2] = tex_colors[y * tex_width * 4 + x * 4 + 0];
|
|
data[y * layout.rowPitch + x * 4 + 3] = tex_colors[y * tex_width * 4 + x * 4 + 3];
|
|
// row[x] = tex_colors[(x & 1) ^ (y & 1)];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (uint32_t y = 0; y < tex_height; y++) {
|
|
// uint32_t *row = (uint32_t *)((char *)data + layout.rowPitch * y);
|
|
for (uint32_t x = 0; x < tex_width; x++) {
|
|
data[y * layout.rowPitch + x * 4 + 0] = tex_colors[y * tex_width * 4 + x * 4 + 0];
|
|
data[y * layout.rowPitch + x * 4 + 1] = tex_colors[y * tex_width * 4 + x * 4 + 1];
|
|
data[y * layout.rowPitch + x * 4 + 2] = tex_colors[y * tex_width * 4 + x * 4 + 2];
|
|
data[y * layout.rowPitch + x * 4 + 3] = tex_colors[y * tex_width * 4 + x * 4 + 3];
|
|
// row[x] = tex_colors[(x & 1) ^ (y & 1)];
|
|
}
|
|
}
|
|
}
|
|
|
|
vkUnmapMemory(vk_ctx.device, tex_obj->mem);
|
|
}
|
|
|
|
if (usage & VK_IMAGE_USAGE_STORAGE_BIT) {
|
|
tex_obj->imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
}
|
|
else {
|
|
tex_obj->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
}
|
|
set_image_layout(tex_obj->image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, tex_obj->imageLayout);
|
|
// setting the image layout does not reference the actual memory so no need to add a mem ref
|
|
}
|
|
|
|
static void destroy_texture_image(struct texture_object *tex_obj) {
|
|
// clean up staging resources
|
|
vkDestroyImage(vk_ctx.device, tex_obj->image, NULL);
|
|
vkFreeMemory(vk_ctx.device, tex_obj->mem, NULL);
|
|
}
|
|
|
|
static VkFormat convert_image_format(kinc_image_format_t format) {
|
|
switch (format) {
|
|
case KINC_IMAGE_FORMAT_RGBA128:
|
|
return VK_FORMAT_R32G32B32A32_SFLOAT;
|
|
case KINC_IMAGE_FORMAT_RGBA64:
|
|
return VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
case KINC_IMAGE_FORMAT_RGB24:
|
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
|
case KINC_IMAGE_FORMAT_A32:
|
|
return VK_FORMAT_R32_SFLOAT;
|
|
case KINC_IMAGE_FORMAT_A16:
|
|
return VK_FORMAT_R16_SFLOAT;
|
|
case KINC_IMAGE_FORMAT_GREY8:
|
|
return VK_FORMAT_R8_UNORM;
|
|
case KINC_IMAGE_FORMAT_BGRA32:
|
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
|
case KINC_IMAGE_FORMAT_RGBA32:
|
|
return VK_FORMAT_R8G8B8A8_UNORM;
|
|
default:
|
|
return VK_FORMAT_B8G8R8A8_UNORM;
|
|
}
|
|
}
|
|
|
|
static int format_byte_size(kinc_image_format_t format) {
|
|
switch (format) {
|
|
case KINC_IMAGE_FORMAT_RGBA128:
|
|
return 16;
|
|
case KINC_IMAGE_FORMAT_RGBA64:
|
|
return 8;
|
|
case KINC_IMAGE_FORMAT_RGB24:
|
|
return 4;
|
|
case KINC_IMAGE_FORMAT_A32:
|
|
return 4;
|
|
case KINC_IMAGE_FORMAT_A16:
|
|
return 2;
|
|
case KINC_IMAGE_FORMAT_GREY8:
|
|
return 1;
|
|
case KINC_IMAGE_FORMAT_BGRA32:
|
|
case KINC_IMAGE_FORMAT_RGBA32:
|
|
return 4;
|
|
default:
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
static void update_stride(kinc_g5_texture_t *texture) {
|
|
VkImageSubresource subres = {0};
|
|
subres.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
subres.mipLevel = 0;
|
|
subres.arrayLayer = 0;
|
|
|
|
VkSubresourceLayout layout;
|
|
vkGetImageSubresourceLayout(vk_ctx.device, texture->impl.texture.image, &subres, &layout);
|
|
|
|
texture->impl.stride = (int)layout.rowPitch;
|
|
}
|
|
|
|
void kinc_g5_texture_init_from_image(kinc_g5_texture_t *texture, kinc_image_t *image) {
|
|
texture->texWidth = image->width;
|
|
texture->texHeight = image->height;
|
|
|
|
const VkFormat tex_format = convert_image_format(image->format);
|
|
VkFormatProperties props;
|
|
VkResult err;
|
|
|
|
vkGetPhysicalDeviceFormatProperties(vk_ctx.gpu, tex_format, &props);
|
|
|
|
if ((props.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) && !use_staging_buffer) {
|
|
// Device can texture using linear textures
|
|
prepare_texture_image((uint8_t *)image->data, (uint32_t)image->width, (uint32_t)image->height, &texture->impl.texture, VK_IMAGE_TILING_LINEAR,
|
|
VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &texture->impl.deviceSize, tex_format);
|
|
|
|
flush_init_cmd();
|
|
}
|
|
else if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
|
|
// Must use staging buffer to copy linear texture to optimized
|
|
struct texture_object staging_texture;
|
|
|
|
memset(&staging_texture, 0, sizeof(staging_texture));
|
|
prepare_texture_image((uint8_t *)image->data, (uint32_t)image->width, (uint32_t)image->height, &staging_texture, VK_IMAGE_TILING_LINEAR,
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &texture->impl.deviceSize, tex_format);
|
|
prepare_texture_image((uint8_t *)image->data, (uint32_t)image->width, (uint32_t)image->height, &texture->impl.texture, VK_IMAGE_TILING_OPTIMAL,
|
|
(VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &texture->impl.deviceSize,
|
|
tex_format);
|
|
set_image_layout(staging_texture.image, VK_IMAGE_ASPECT_COLOR_BIT, staging_texture.imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
|
set_image_layout(texture->impl.texture.image, VK_IMAGE_ASPECT_COLOR_BIT, texture->impl.texture.imageLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
|
|
VkImageCopy copy_region = {0};
|
|
copy_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
copy_region.srcSubresource.mipLevel = 0;
|
|
copy_region.srcSubresource.baseArrayLayer = 0;
|
|
copy_region.srcSubresource.layerCount = 1;
|
|
copy_region.srcOffset.x = copy_region.srcOffset.y = copy_region.srcOffset.z = 0;
|
|
copy_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
copy_region.dstSubresource.mipLevel = 0;
|
|
copy_region.dstSubresource.baseArrayLayer = 0;
|
|
copy_region.dstSubresource.layerCount = 1;
|
|
copy_region.dstOffset.x = copy_region.dstOffset.y = copy_region.dstOffset.z = 0;
|
|
copy_region.extent.width = (uint32_t)staging_texture.tex_width;
|
|
copy_region.extent.height = (uint32_t)staging_texture.tex_height;
|
|
copy_region.extent.depth = 1;
|
|
|
|
vkCmdCopyImage(vk_ctx.setup_cmd, staging_texture.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, texture->impl.texture.image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region);
|
|
|
|
set_image_layout(texture->impl.texture.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, texture->impl.texture.imageLayout);
|
|
|
|
flush_init_cmd();
|
|
|
|
destroy_texture_image(&staging_texture);
|
|
}
|
|
else {
|
|
assert(!"No support for B8G8R8A8_UNORM as texture image format");
|
|
}
|
|
|
|
update_stride(texture);
|
|
|
|
VkImageViewCreateInfo view = {0};
|
|
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
view.pNext = NULL;
|
|
view.image = VK_NULL_HANDLE;
|
|
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view.format = tex_format;
|
|
view.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
view.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
view.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
view.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view.subresourceRange.baseMipLevel = 0;
|
|
view.subresourceRange.levelCount = 1;
|
|
view.subresourceRange.baseArrayLayer = 0;
|
|
view.subresourceRange.layerCount = 1;
|
|
view.flags = 0;
|
|
|
|
view.image = texture->impl.texture.image;
|
|
err = vkCreateImageView(vk_ctx.device, &view, NULL, &texture->impl.texture.view);
|
|
assert(!err);
|
|
}
|
|
|
|
void kinc_g5_texture_init(kinc_g5_texture_t *texture, int width, int height, kinc_image_format_t format) {
|
|
texture->texWidth = width;
|
|
texture->texHeight = height;
|
|
|
|
const VkFormat tex_format = convert_image_format(format);
|
|
VkFormatProperties props;
|
|
VkResult err;
|
|
|
|
vkGetPhysicalDeviceFormatProperties(vk_ctx.gpu, tex_format, &props);
|
|
|
|
// Device can texture using linear textures
|
|
prepare_texture_image(NULL, (uint32_t)width, (uint32_t)height, &texture->impl.texture, VK_IMAGE_TILING_LINEAR, VK_IMAGE_USAGE_SAMPLED_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &texture->impl.deviceSize, tex_format);
|
|
|
|
flush_init_cmd();
|
|
|
|
update_stride(texture);
|
|
|
|
VkImageViewCreateInfo view = {0};
|
|
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
view.pNext = NULL;
|
|
view.image = VK_NULL_HANDLE;
|
|
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view.format = tex_format;
|
|
view.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
view.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
view.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
view.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view.subresourceRange.baseMipLevel = 0;
|
|
view.subresourceRange.levelCount = 1;
|
|
view.subresourceRange.baseArrayLayer = 0;
|
|
view.subresourceRange.layerCount = 1;
|
|
view.flags = 0;
|
|
|
|
view.image = texture->impl.texture.image;
|
|
err = vkCreateImageView(vk_ctx.device, &view, NULL, &texture->impl.texture.view);
|
|
assert(!err);
|
|
}
|
|
|
|
void kinc_g5_texture_init3d(kinc_g5_texture_t *texture, int width, int height, int depth, kinc_image_format_t format) {}
|
|
|
|
void kinc_g5_texture_init_non_sampled_access(kinc_g5_texture_t *texture, int width, int height, kinc_image_format_t format) {
|
|
texture->texWidth = width;
|
|
texture->texHeight = height;
|
|
|
|
const VkFormat tex_format = convert_image_format(format);
|
|
VkFormatProperties props;
|
|
VkResult err;
|
|
|
|
vkGetPhysicalDeviceFormatProperties(vk_ctx.gpu, tex_format, &props);
|
|
|
|
// Device can texture using linear textures
|
|
prepare_texture_image(NULL, (uint32_t)width, (uint32_t)height, &texture->impl.texture, VK_IMAGE_TILING_OPTIMAL,
|
|
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &texture->impl.deviceSize,
|
|
tex_format);
|
|
|
|
flush_init_cmd();
|
|
|
|
update_stride(texture);
|
|
|
|
VkImageViewCreateInfo view = {0};
|
|
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
view.pNext = NULL;
|
|
view.image = VK_NULL_HANDLE;
|
|
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view.format = tex_format;
|
|
view.components.r = VK_COMPONENT_SWIZZLE_R;
|
|
view.components.g = VK_COMPONENT_SWIZZLE_G;
|
|
view.components.b = VK_COMPONENT_SWIZZLE_B;
|
|
view.components.a = VK_COMPONENT_SWIZZLE_A;
|
|
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
view.subresourceRange.baseMipLevel = 0;
|
|
view.subresourceRange.levelCount = 1;
|
|
view.subresourceRange.baseArrayLayer = 0;
|
|
view.subresourceRange.layerCount = 1;
|
|
view.flags = 0;
|
|
|
|
view.image = texture->impl.texture.image;
|
|
err = vkCreateImageView(vk_ctx.device, &view, NULL, &texture->impl.texture.view);
|
|
assert(!err);
|
|
}
|
|
|
|
void kinc_g5_texture_destroy(kinc_g5_texture_t *texture) {
|
|
vkDestroyImageView(vk_ctx.device, texture->impl.texture.view, NULL);
|
|
destroy_texture_image(&texture->impl.texture);
|
|
}
|
|
|
|
void kinc_g5_internal_texture_set(kinc_g5_texture_t *texture, int unit) {}
|
|
|
|
int kinc_g5_texture_stride(kinc_g5_texture_t *texture) {
|
|
return texture->impl.stride;
|
|
}
|
|
|
|
uint8_t *kinc_g5_texture_lock(kinc_g5_texture_t *texture) {
|
|
void *data;
|
|
|
|
VkResult err = vkMapMemory(vk_ctx.device, texture->impl.texture.mem, 0, texture->impl.deviceSize, 0, &data);
|
|
assert(!err);
|
|
|
|
return (uint8_t *)data;
|
|
}
|
|
|
|
void kinc_g5_texture_unlock(kinc_g5_texture_t *texture) {
|
|
vkUnmapMemory(vk_ctx.device, texture->impl.texture.mem);
|
|
}
|
|
|
|
void kinc_g5_texture_clear(kinc_g5_texture_t *texture, int x, int y, int z, int width, int height, int depth, unsigned color) {}
|
|
|
|
void kinc_g5_texture_generate_mipmaps(kinc_g5_texture_t *texture, int levels) {}
|
|
|
|
void kinc_g5_texture_set_mipmap(kinc_g5_texture_t *texture, kinc_image_t *mipmap, int level) {}
|