Sketch out Texture uploads;

This commit is contained in:
bjorn 2022-04-30 18:49:46 -07:00
parent b7aa3f29a4
commit ebe77e5924
4 changed files with 160 additions and 22 deletions

View File

@ -5,6 +5,7 @@
#include "util.h"
#include <lua.h>
#include <lauxlib.h>
#include <stdlib.h>
#include <string.h>
StringEntry lovrBufferLayout[] = {
@ -444,7 +445,6 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
int index = 1;
Image* stack[6];
Image** images = stack;
uint32_t imageCount = 0;
if (lua_isnumber(L, 1)) {
info.width = luax_checku32(L, index++);
@ -454,14 +454,14 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
info.type = TEXTURE_ARRAY;
}
} else if (lua_istable(L, 1)) {
imageCount = luax_len(L, index++);
images = imageCount > COUNTOF(stack) ? malloc(imageCount * sizeof(Image*)) : stack;
info.imageCount = luax_len(L, index++);
images = info.imageCount > COUNTOF(stack) ? malloc(info.imageCount * sizeof(Image*)) : stack;
lovrAssert(images, "Out of memory");
info.type = TEXTURE_ARRAY;
info.depth = imageCount;
info.depth = info.imageCount;
if (imageCount == 0) {
imageCount = 6;
if (info.imageCount == 0) {
info.imageCount = 6;
info.type = TEXTURE_CUBE;
const char* faces[6] = { "right", "left", "top", "bottom", "back", "front" };
const char* altFaces[6] = { "px", "nx", "py", "ny", "pz", "nz" };
@ -477,8 +477,10 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
images[i] = luax_checkimage(L, -1);
}
}
lovrCheck(lovrImageGetLayerCount(images[0]) == 1, "When a list of images is provided, each must have a single layer");
} else {
imageCount = 1;
info.imageCount = 1;
images[0] = luax_checkimage(L, index++);
info.depth = lovrImageGetLayerCount(images[0]);
if (lovrImageIsCube(images[0])) {
@ -488,7 +490,8 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
}
}
if (imageCount > 0) {
if (info.imageCount > 0) {
info.images = images;
Image* image = images[0];
uint32_t layers = lovrImageGetLayerCount(image);
uint32_t levels = lovrImageGetLevelCount(image);
@ -497,11 +500,12 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
info.height = lovrImageGetHeight(image);
info.mipmaps = levels == 1 ? ~0u : levels;
info.srgb = lovrImageIsSRGB(image);
for (uint32_t i = 1; i < imageCount; i++) {
for (uint32_t i = 1; i < info.imageCount; i++) {
lovrAssert(lovrImageGetWidth(images[0]) == lovrImageGetWidth(images[i]), "Image widths must match");
lovrAssert(lovrImageGetHeight(images[0]) == lovrImageGetHeight(images[i]), "Image heights must match");
lovrAssert(lovrImageGetFormat(images[0]) == lovrImageGetFormat(images[i]), "Image formats must match");
lovrAssert(lovrImageGetLevelCount(images[0]) == lovrImageGetLevelCount(images[i]), "Image mipmap counts must match");
lovrAssert(lovrImageGetLayerCount(images[i]) == 1, "When a list of images are provided, each must have a single layer");
}
}
@ -510,7 +514,7 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
info.type = lua_isnil(L, -1) ? info.type : luax_checkenum(L, -1, TextureType, NULL);
lua_pop(L, 1);
if (imageCount == 0) {
if (info.imageCount == 0) {
lua_getfield(L, index, "format");
info.format = lua_isnil(L, -1) ? info.format : luax_checkenum(L, -1, TextureFormat, NULL);
lua_pop(L, 1);
@ -546,7 +550,7 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
}
break;
}
case LUA_TNIL: info.usage = (imageCount == 0) ? TEXTURE_RENDER | TEXTURE_SAMPLE : TEXTURE_SAMPLE; break;
case LUA_TNIL: info.usage = (info.imageCount == 0) ? TEXTURE_RENDER | TEXTURE_SAMPLE : TEXTURE_SAMPLE; break;
default: return luaL_error(L, "Expected Texture usage to be a string, table, or nil");
}
lua_pop(L, 1);
@ -556,18 +560,18 @@ static int l_lovrGraphicsNewTexture(lua_State* L) {
lua_pop(L, 1);
}
if (imageCount == 0 && info.depth == 0) {
if (info.imageCount == 0 && info.depth == 0) {
info.depth = info.type == TEXTURE_CUBE ? 6 : 1;
}
Texture* texture = lovrTextureCreate(&info);
for (uint32_t i = 0; i < imageCount; i++) {
lovrRelease(info.images[i], lovrImageDestroy);
for (uint32_t i = 0; i < info.imageCount; i++) {
lovrRelease(images[i], lovrImageDestroy);
}
if (info.images != stack) {
free(info.images);
if (images != stack) {
free(images);
}
luax_pushtype(L, Texture, texture);

View File

@ -477,6 +477,97 @@ bool gpu_texture_init(gpu_texture* texture, gpu_texture_info* info) {
return false;
}
if (info->upload.stream) {
VkImage image = texture->handle;
VkCommandBuffer commands = info->upload.stream->commands;
uint32_t levelCount = info->upload.levelCount;
gpu_buffer* buffer = info->upload.buffer;
VkPipelineStageFlags prev, next;
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkImageMemoryBarrier transition = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.image = image,
.subresourceRange.aspectMask = texture->aspect,
.subresourceRange.baseMipLevel = 0,
.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS,
.subresourceRange.baseArrayLayer = 0,
.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS
};
if (levelCount > 0) {
VkBufferImageCopy regions[16];
for (uint32_t i = 0; i < levelCount; i++) {
regions[i] = (VkBufferImageCopy) {
.bufferOffset = buffer->offset + info->upload.levelOffsets[i],
.imageSubresource.aspectMask = texture->aspect,
.imageSubresource.mipLevel = i,
.imageSubresource.baseArrayLayer = 0,
.imageSubresource.layerCount = texture->layered ? info->size[2] : 1,
.imageExtent.width = MAX(info->size[0] >> i, 1),
.imageExtent.height = MAX(info->size[1] >> i, 1),
.imageExtent.depth = texture->layered ? 1 : MAX(info->size[2] >> i, 1)
};
}
// Upload initial contents
prev = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
next = VK_PIPELINE_STAGE_TRANSFER_BIT;
transition.srcAccessMask = 0;
transition.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
transition.oldLayout = layout;
transition.newLayout = layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
vkCmdPipelineBarrier(commands, prev, next, 0, 0, NULL, 0, NULL, 1, &transition);
vkCmdCopyBufferToImage(commands, buffer->handle, image, layout, levelCount, regions);
// Generate mipmaps
if (info->upload.generateMipmaps) {
prev = VK_PIPELINE_STAGE_TRANSFER_BIT;
next = VK_PIPELINE_STAGE_TRANSFER_BIT;
transition.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
transition.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
transition.subresourceRange.baseMipLevel = 0;
transition.subresourceRange.levelCount = levelCount;
transition.oldLayout = layout;
transition.newLayout = layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vkCmdPipelineBarrier(commands, prev, next, 0, 0, NULL, 0, NULL, 1, &transition);
for (uint32_t i = levelCount; i < info->mipmaps; i++) {
VkImageBlit region = {
.srcSubresource = {
.aspectMask = texture->aspect,
.mipLevel = i - 1,
.layerCount = texture->layered ? info->size[2] : 1
},
.dstSubresource = {
.aspectMask = texture->aspect,
.mipLevel = i,
.layerCount = texture->layered ? info->size[2] : 1
},
.srcOffsets[1] = { MAX(info->size[0] >> (i - 1), 1), MAX(info->size[1] >> (i - 1), 1), 1 },
.dstOffsets[1] = { MAX(info->size[0] >> i, 1), MAX(info->size[1] >> i, 1), 1 }
};
vkCmdBlitImage(commands, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region, VK_FILTER_LINEAR);
transition.subresourceRange.baseMipLevel = i;
transition.subresourceRange.levelCount = 1;
transition.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
transition.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
vkCmdPipelineBarrier(commands, prev, next, 0, 0, NULL, 0, NULL, 1, &transition);
}
}
}
// Transition to natural layout
prev = levelCount > 0 ? VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
next = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
transition.srcAccessMask = levelCount > 0 ? VK_ACCESS_TRANSFER_WRITE_BIT : 0;
transition.dstAccessMask = 0;
transition.oldLayout = layout;
transition.newLayout = texture->layout;
transition.subresourceRange.baseMipLevel = 0;
transition.subresourceRange.levelCount = info->mipmaps;
vkCmdPipelineBarrier(commands, prev, next, 0, 0, NULL, 0, NULL, 1, &transition);
}
texture->memory = memory - state.memory;
return true;

View File

@ -292,7 +292,8 @@ Texture* lovrTextureCreate(TextureInfo* info) {
};
uint32_t limit = limits[info->type];
uint32_t mips = log2(MAX(MAX(info->width, info->height), (info->type == TEXTURE_3D ? info->depth : 1))) + 1;
uint32_t mipmapCap = log2(MAX(MAX(info->width, info->height), (info->type == TEXTURE_3D ? info->depth : 1))) + 1;
uint32_t mipmaps = CLAMP(info->mipmaps, 1, mipmapCap);
uint8_t supports = state.features.formats[info->format];
lovrCheck(info->width > 0, "Texture width must be greater than zero");
@ -305,18 +306,18 @@ Texture* lovrTextureCreate(TextureInfo* info) {
lovrCheck(info->depth == 1 || info->type != TEXTURE_2D, "2D textures must have a depth of 1");
lovrCheck(info->depth == 6 || info->type != TEXTURE_CUBE, "Cubemaps must have a depth of 6");
lovrCheck(info->width == info->height || info->type != TEXTURE_CUBE, "Cubemaps must be square");
lovrCheck(measureTexture(info->format, info->width, info->height, info->depth) < 1 << 30, "Memory for a Texture can not exceed 1GB");
lovrCheck(measureTexture(info->format, info->width, info->height, info->depth) < 1 << 30, "Memory for a Texture can not exceed 1GB"); // TODO mip?
lovrCheck(info->samples == 1 || info->samples == 4, "Currently, Texture multisample count must be 1 or 4");
lovrCheck(info->samples == 1 || info->type != TEXTURE_CUBE, "Cubemaps can not be multisampled");
lovrCheck(info->samples == 1 || info->type != TEXTURE_3D, "Volume textures can not be multisampled");
lovrCheck(info->samples == 1 || ~info->usage & TEXTURE_STORAGE, "Currently, Textures with the 'storage' flag can not be multisampled");
lovrCheck(info->samples == 1 || info->mipmaps == 1, "Multisampled textures can only have 1 mipmap");
lovrCheck(info->samples == 1 || mipmaps == 1, "Multisampled textures can only have 1 mipmap");
lovrCheck(~info->usage & TEXTURE_SAMPLE || (supports & GPU_FEATURE_SAMPLE), "GPU does not support the 'sample' flag for this format");
lovrCheck(~info->usage & TEXTURE_RENDER || (supports & GPU_FEATURE_RENDER), "GPU does not support the 'render' flag for this format");
lovrCheck(~info->usage & TEXTURE_STORAGE || (supports & GPU_FEATURE_STORAGE), "GPU does not support the 'storage' flag for this format");
lovrCheck(~info->usage & TEXTURE_RENDER || info->width <= state.limits.renderSize[0], "Texture has 'render' flag but its size exceeds the renderSize limit");
lovrCheck(~info->usage & TEXTURE_RENDER || info->height <= state.limits.renderSize[1], "Texture has 'render' flag but its size exceeds the renderSize limit");
lovrCheck(info->mipmaps == ~0u || info->mipmaps <= mips, "Texture has more than the max number of mipmap levels for its size (%d)", mips);
lovrCheck(mipmaps <= mipmapCap, "Texture has more than the max number of mipmap levels for its size (%d)", mipmapCap);
lovrCheck((info->format < FORMAT_BC1 || info->format > FORMAT_BC7) || state.features.textureBC, "%s textures are not supported on this GPU", "BC");
lovrCheck(info->format < FORMAT_ASTC_4x4 || state.features.textureASTC, "%s textures are not supported on this GPU", "ASTC");
@ -325,7 +326,41 @@ Texture* lovrTextureCreate(TextureInfo* info) {
texture->ref = 1;
texture->gpu = (gpu_texture*) (texture + 1);
texture->info = *info;
texture->info.mipmaps = CLAMP(texture->info.mipmaps, 1, mips);
texture->info.mipmaps = mipmaps;
uint32_t levelCount = 0;
uint32_t levelOffsets[16];
uint32_t levelSizes[16];
gpu_buffer* scratchpad;
if (info->imageCount > 0) {
levelCount = lovrImageGetLevelCount(info->images[0]);
lovrCheck(info->type != TEXTURE_3D || levelCount == 1, "Images used to initialize 3D textures can not have mipmaps");
uint32_t total = 0;
for (uint32_t level = 0; level < levelCount; level++) {
levelOffsets[level] = total;
uint32_t width = MAX(info->width >> level, 1);
uint32_t height = MAX(info->height >> level, 1);
levelSizes[level] = measureTexture(info->format, width, height, info->depth);
total += levelSizes[level];
}
scratchpad = tempAlloc(gpu_sizeof_buffer());
char* data = gpu_map(scratchpad, total, 64, GPU_MAP_WRITE);
for (uint32_t level = 0; level < levelCount; level++) {
for (uint32_t layer = 0; layer < info->depth; layer++) {
Image* image = info->imageCount == 1 ? info->images[0] : info->images[layer];
uint32_t slice = info->imageCount == 1 ? layer : 0;
uint32_t size = lovrImageGetLayerSize(image, level);
lovrCheck(size == levelSizes[level], "Texture/Image size mismatch!");
void* pixels = lovrImageGetLayerData(image, level, layer);
memcpy(data, pixels, size);
data += size;
}
}
}
gpu_texture_init(texture->gpu, &(gpu_texture_info) {
.type = (gpu_texture_type) info->type,
@ -340,7 +375,14 @@ Texture* lovrTextureCreate(TextureInfo* info) {
((info->usage & TEXTURE_COPY) ? GPU_TEXTURE_COPY_SRC | GPU_TEXTURE_COPY_DST : 0),
.srgb = info->srgb,
.handle = info->handle,
.label = info->label
.label = info->label,
.upload = {
.stream = getTransfers(),
.buffer = scratchpad,
.levelCount = levelCount,
.levelOffsets = levelOffsets,
.generateMipmaps = levelCount < mipmaps
}
});
// Automatically create a renderable view for renderable non-volume textures

View File

@ -182,6 +182,7 @@ typedef struct {
uint32_t usage;
bool srgb;
uintptr_t handle;
uint32_t imageCount;
struct Image** images;
const char* label;
} TextureInfo;