Skip to content

Commit

Permalink
vk: refactor VulkanProgram (#7221)
Browse files Browse the repository at this point in the history
  • Loading branch information
poweifeng authored Oct 3, 2023
1 parent 5572097 commit 2cf8645
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 125 deletions.
9 changes: 5 additions & 4 deletions filament/backend/src/vulkan/VulkanBlitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ void VulkanBlitter::lazyInit() noexcept {

VkShaderModule vertexShader = decode(VKSHADERS_BLITDEPTHVS_DATA, VKSHADERS_BLITDEPTHVS_SIZE);
VkShaderModule fragmentShader = decode(VKSHADERS_BLITDEPTHFS_DATA, VKSHADERS_BLITDEPTHFS_SIZE);
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader);

// Allocate one anonymous sampler at slot 0.
mDepthResolveProgram->samplerGroupInfo[0].samplers.reserve(1);
mDepthResolveProgram->samplerGroupInfo[0].samplers.resize(1);
VulkanProgram::CustomSamplerInfoList samplers = {
{0, 0, ShaderStageFlags::FRAGMENT},
};
mDepthResolveProgram = new VulkanProgram(mDevice, vertexShader, fragmentShader, samplers);

#if FVK_ENABLED(FVK_DEBUG_BLITTER)
utils::slog.d << "Created Shader Module for VulkanBlitter "
Expand Down Expand Up @@ -359,7 +360,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
// DRAW THE TRIANGLE
// -----------------

mPipelineCache.bindProgram(*mDepthResolveProgram);
mPipelineCache.bindProgram(mDepthResolveProgram);
mPipelineCache.bindPrimitiveTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP);

auto vkraster = mPipelineCache.getCurrentRasterState();
Expand Down
101 changes: 51 additions & 50 deletions filament/backend/src/vulkan/VulkanDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1548,7 +1548,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets();

// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
mPipelineCache.bindProgram(*program);
mPipelineCache.bindProgram(program);
mPipelineCache.bindRasterState(mPipelineCache.getCurrentRasterState());
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount);
Expand All @@ -1560,68 +1560,69 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};

VulkanPipelineCache::UsageFlags usage;
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
VulkanPipelineCache::UsageFlags usage = program->getUsage();

UTILS_NOUNROLL
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT; samplerGroupIdx++) {
const auto& samplerGroup = program->samplerGroupInfo[samplerGroupIdx];
const auto& samplers = samplerGroup.samplers;
if (samplers.empty()) {
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
uint16_t const indexPair = bindingToSamplerIndex[binding];

if (indexPair == 0xffff) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupIdx];

uint16_t const samplerGroupInd = (indexPair >> 8) & 0xff;
uint16_t const samplerInd = (indexPair & 0xff);

VulkanSamplerGroup* vksb = mSamplerBindings[samplerGroupInd];
if (!vksb) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}
SamplerGroup* sb = vksb->sb.get();
assert_invariant(sb->getSize() == samplers.size());
size_t samplerIdx = 0;
for (auto& sampler : samplers) {
const SamplerDescriptor* boundSampler = sb->data() + samplerIdx;
samplerIdx++;
SamplerDescriptor const* boundSampler = ((SamplerDescriptor*) vksb->sb->data()) + samplerInd;

if (UTILS_LIKELY(boundSampler->t)) {
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
VkImageViewType const expectedType = texture->getViewType();
if (UTILS_UNLIKELY(!boundSampler->t)) {
usage = VulkanPipelineCache::disableUsageFlags(binding, usage);
continue;
}

VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
VkImageViewType const expectedType = texture->getViewType();

// TODO: can this uninitialized check be checked in a higher layer?
// This fallback path is very flaky because the dummy texture might not have
// matching characteristics. (e.g. if the missing texture is a 3D texture)
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
// TODO: can this uninitialized check be checked in a higher layer?
// This fallback path is very flaky because the dummy texture might not have
// matching characteristics. (e.g. if the missing texture is a 3D texture)
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
#endif
texture = mEmptyTexture.get();
}

const SamplerParams& samplerParams = boundSampler->s;
VkSampler vksampler = mSamplerCache.getSampler(samplerParams);

usage = VulkanPipelineCache::getUsageFlags(sampler.binding, samplerGroup.stageFlags, usage);

VkImageView imageView = VK_NULL_HANDLE;
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)
&& expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level
// *can* be an attachment, then the sampler for this texture has the same view
// properties as a view for an attachment. Therefore, we can use
// getAttachmentView to get a corresponding VkImageView.
imageView = texture->getAttachmentView(range);
} else {
imageView = texture->getViewForType(range, expectedType);
}
texture = mEmptyTexture.get();
}

samplerInfo[sampler.binding] = {
.sampler = vksampler,
.imageView = imageView,
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
};
samplerTextures[sampler.binding] = texture;
}
SamplerParams const& samplerParams = boundSampler->s;
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
VkImageView imageView = VK_NULL_HANDLE;
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
// an attachment, then the sampler for this texture has the same view properties as a
// view for an attachment. Therefore, we can use getAttachmentView to get a
// corresponding VkImageView.
imageView = texture->getAttachmentView(range);
} else {
imageView = texture->getViewForType(range, expectedType);
}

samplerInfo[binding] = {
.sampler = vksampler,
.imageView = imageView,
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
};
samplerTextures[binding] = texture;
}

mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
Expand Down
130 changes: 71 additions & 59 deletions filament/backend/src/vulkan/VulkanHandles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,88 +49,100 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
: HwProgram(builder.getName()),
VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo(builder.getSpecializationConstants().size())),
mDevice(device) {
auto const& blobs = builder.getShadersSource();
VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment};
// TODO: handle compute shaders.
for (size_t i = 0; i < 2; i++) {
auto& blobs = builder.getShadersSource();
auto& modules = mInfo->shaders;
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
const auto& blob = blobs[i];
VkShaderModule* module = modules[i];
VkShaderModuleCreateInfo moduleInfo = {};
moduleInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleInfo.codeSize = blob.size();
moduleInfo.pCode = (uint32_t*) blob.data();
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, module);
uint32_t* data = (uint32_t*)blob.data();
VkShaderModule& module = modules[i];
VkShaderModuleCreateInfo moduleInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = blob.size(),
.pCode = data,
};
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
}

// Note that bools are 4-bytes in Vulkan
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
constexpr uint32_t const CONSTANT_SIZE = 4;

// populate the specialization constants requirements right now
auto const& specializationConstants = builder.getSpecializationConstants();
if (!specializationConstants.empty()) {
// Allocate a single heap block to store all the specialization constants structures
// our supported types are int32, float and bool, so we use 4 bytes per data. bool will
// just use the first byte.
char* pStorage = (char*)malloc(
sizeof(VkSpecializationInfo) +
specializationConstants.size() * sizeof(VkSpecializationMapEntry) +
specializationConstants.size() * 4);

VkSpecializationInfo* const pInfo = (VkSpecializationInfo*)pStorage;
VkSpecializationMapEntry* const pEntries =
(VkSpecializationMapEntry*)(pStorage + sizeof(VkSpecializationInfo));
void* pData = pStorage + sizeof(VkSpecializationInfo) +
specializationConstants.size() * sizeof(VkSpecializationMapEntry);

*pInfo = {
.mapEntryCount = specializationConstants.size(),
.pMapEntries = pEntries,
.dataSize = specializationConstants.size() * 4,
.pData = pData,
uint32_t const specConstCount = static_cast<uint32_t>(specializationConstants.size());
char* specData = mInfo->specConstData.get();
if (specConstCount > 0) {
mInfo->specializationInfo = {
.mapEntryCount = specConstCount,
.pMapEntries = mInfo->specConsts.data(),
.dataSize = specConstCount * CONSTANT_SIZE,
.pData = specData,
};
}
for (uint32_t i = 0; i < specConstCount; ++i) {
uint32_t const offset = i * CONSTANT_SIZE;
mInfo->specConsts[i] = {
.constantID = specializationConstants[i].id,
.offset = offset,
.size = CONSTANT_SIZE,
};
using SpecConstant = Program::SpecializationConstant::Type;
char const* addr = (char*)specData + offset;
SpecConstant const& arg = specializationConstants[i].value;
if (std::holds_alternative<bool>(arg)) {
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
} else if (std::holds_alternative<float>(arg)) {
*((float*)addr) = std::get<float>(arg);
} else {
*((int32_t*)addr) = std::get<int32_t>(arg);
}
}

for (size_t i = 0; i < specializationConstants.size(); i++) {
uint32_t const offset = uint32_t(i) * 4;
pEntries[i] = {
.constantID = specializationConstants[i].id,
.offset = offset,
// Note that bools are 4-bytes in Vulkan
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
.size = 4,
};

using SpecConstant = Program::SpecializationConstant::Type;
char const* addr = (char*)pData + offset;
SpecConstant const& arg = specializationConstants[i].value;
if (std::holds_alternative<bool>(arg)) {
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
} else if (std::holds_alternative<float>(arg)) {
*((float*)addr) = std::get<float>(arg);
} else {
*((int32_t*)addr) = std::get<int32_t>(arg);
}
auto& groupInfo = builder.getSamplerGroupInfo();
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& usage = mInfo->usage;
for (uint8_t groupInd = 0; groupInd < Program::SAMPLER_BINDING_COUNT; groupInd++) {
auto const& group = groupInfo[groupInd];
auto const& samplers = group.samplers;
for (size_t i = 0; i < samplers.size(); ++i) {
uint32_t const binding = samplers[i].binding;
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
}
bundle.specializationInfos = pInfo;
}

// Make a copy of the binding map
samplerGroupInfo = builder.getSamplerGroupInfo();
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
<< ", " << bundle.fragment << ")" << utils::io::endl;
#endif
}

VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept
: VulkanResource(VulkanResourceType::PROGRAM),
mInfo(new PipelineInfo(0)),
mDevice(device) {
bundle.vertex = vs;
bundle.fragment = fs;
mInfo->shaders[0] = vs;
mInfo->shaders[1] = fs;
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
auto& usage = mInfo->usage;
bindingToSamplerIndex.resize(samplerInfo.size());
for (uint16_t binding = 0; binding < samplerInfo.size(); ++binding) {
auto const& sampler = samplerInfo[binding];
bindingToSamplerIndex[binding]
= (sampler.groupIndex << 8) | (0xff & sampler.samplerIndex);
usage = VulkanPipelineCache::getUsageFlags(binding, sampler.flags, usage);
}
}

VulkanProgram::~VulkanProgram() {
vkDestroyShaderModule(mDevice, bundle.vertex, VKALLOC);
vkDestroyShaderModule(mDevice, bundle.fragment, VKALLOC);
free(bundle.specializationInfos);
for (auto shader: mInfo->shaders) {
vkDestroyShaderModule(mDevice, shader, VKALLOC);
}
delete mInfo;
}

// Creates a special "default" render target (i.e. associated with the swap chain)
Expand Down
58 changes: 54 additions & 4 deletions filament/backend/src/vulkan/VulkanHandles.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,64 @@ namespace filament::backend {
class VulkanTimestamps;

struct VulkanProgram : public HwProgram, VulkanResource {

VulkanProgram(VkDevice device, const Program& builder) noexcept;
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;

struct CustomSamplerInfo {
uint8_t groupIndex;
uint8_t samplerIndex;
ShaderStageFlags flags;
};
using CustomSamplerInfoList = utils::FixedCapacityVector<CustomSamplerInfo>;

// We allow custom descriptor of the samplers within shaders. This is needed if we want to use
// a program that exists only in the backend - for example, for shader-based bliting.
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
CustomSamplerInfoList const& samplerInfo) noexcept;
~VulkanProgram();
VulkanPipelineCache::ProgramBundle bundle;
Program::SamplerGroupInfo samplerGroupInfo;

inline VkShaderModule getVertexShader() const {
return mInfo->shaders[0];
}

inline VkShaderModule getFragmentShader() const { return mInfo->shaders[1]; }

inline VulkanPipelineCache::UsageFlags getUsage() const { return mInfo->usage; }

inline utils::FixedCapacityVector<uint16_t> const& getBindingToSamplerIndex() const {
return mInfo->bindingToSamplerIndex;
}

inline VkSpecializationInfo const& getSpecConstInfo() const {
return mInfo->specializationInfo;
}

private:
VkDevice mDevice;
// TODO: handle compute shaders.
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
static constexpr uint8_t MAX_SHADER_MODULES = 2;

struct PipelineInfo {
PipelineInfo(size_t specConstsCount) :
bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
specConsts(specConstsCount, VkSpecializationMapEntry{}),
specConstData(new char[specConstsCount * 4])
{}

// This bitset maps to each of the sampler in the sampler groups associated with this
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
VulkanPipelineCache::UsageFlags usage;

// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE};
VkSpecializationInfo specializationInfo = {};
utils::FixedCapacityVector<VkSpecializationMapEntry> specConsts;
std::unique_ptr<char[]> specConstData;
};

PipelineInfo* mInfo;
VkDevice mDevice = VK_NULL_HANDLE;
};

// The render target bundles together a set of attachments, each of which can have one of the
Expand Down
Loading

0 comments on commit 2cf8645

Please sign in to comment.