Compare commits

..
Author SHA1 Message Date
Powei Feng 6474cb0bcd vk: fixing renderstandalone 2024-10-09 16:27:50 -07:00
Mathias Agopian 6653c6c08b fix a typo in RenderPassBuilder::renderFlags() 2024-08-28 21:51:03 -07:00
Maximilien Dagois 777f664b1b Refactored the handling of usage flags in the VulkanTexture constructor 2024-08-28 12:58:31 -07:00
Powei Feng 283d240409 Re-enable DebugRegistry::setProperty for release build (#8086)
`DebugRegistry::setProperty` is no longer just applicable to debug
builds.

This change was previously added in 6c0bd36 but then reverted
in a7317e7. We re-enable it and separate it from the shadow
changes in this commit.
2024-08-28 06:17:26 +00:00
Sungun Park 0e0f3a5518 Release Filament 1.54.1 2024-08-27 23:30:26 +00:00
Powei Feng ba5413622f Decouple subpass from buffer padding (#8085)
Coupling subpass on/off with buffer padding has unintended
consequences when we need to turn subpass off for tests. We
uncouple them in this change.
2024-08-27 22:33:31 +00:00
Ben Doherty ba8d429fcb Metal: annotate memory allocation failures with buffer tag (#8082) 2024-08-27 10:49:35 -07:00
Mathias Agopian f11e5cb081 remove uses of the blackboard
the framegraph blackboard tends to create a lot of problems hard to 
debug, so we are now more explicit. here we remove usages of the
blackboard in RenderUtils.cpp.
2024-08-27 09:55:00 -07:00
Mathias Agopian c84f5d2a7f fix screen-space reflection when sub-pass colorgrading is used
the problem was that in that case, SSR was using the colorgraded
color buffer as history.
2024-08-27 09:55:00 -07:00
Sungun Park a7317e7a99 Revert two depth relevant changes (#8083)
This reverts commits
- b70aa43727 "depth clamp cannot work with VSM"
- 6c0bd360b3 "Add support for depth clamp and use it for shadows"
2024-08-26 23:30:15 +00:00
Mathias Agopian be4391950d fix gltfio ubershaders
default parameters were not initialized which could cause them to
be incorrectly evaluated in the shader. this is actually a pretty
crazy bug that has been around since forever and which we were
"lucky" to not run into sooner.

this was exposed with the specular extension that was added recently.
2024-08-26 10:24:42 -07:00
Ben Doherty 6f20cf4b02 Support tagging driver handles with a name (#8038) 2024-08-24 09:17:05 -07:00
Mathias Agopian 9674a5ae3c add support for subresources in "blit"
This is needed for debugging, but is a pretty low-hanging fruit.
2024-08-23 22:37:01 -07:00
Balaji MandPowei Feng 485c05789b return statement moved to separate line
Co-authored-by: Powei Feng <powei@google.com>
2024-08-23 11:02:19 -07:00
Balaji M b058794dd1 decoding meshoptimized gltf just once 2024-08-23 11:02:19 -07:00
Powei Feng be22e9305d matc: initialize CustomVariable (#8076)
The default values were not provided via default construction.
Caused matc to crash on Linux.
2024-08-23 16:09:56 +00:00
Sungun Park 4ae7aa6a1b Fix compatibility warning for multiview (#8075)
Verify the compatibility between a compiled material and the engine's
setting only when the engine is set up for stereo.

Default materials are always compiled with either 'instanced' or
'multiview. Therefore Filament will emit warnings unintentionally if the
engine is set up for stereo. This commit fixes it.
2024-08-22 20:39:04 +00:00
Mathias Agopian 5e7106b521 custom variables can now have their precision specified
e.g.:

    variables : [
        {
            name : vertex,
            precision : medium,
         }
    ],
2024-08-22 13:08:48 -07:00
Mathias Agopian 30387af61c reenable the SimplificationPass in spirv-opt
Issues with it were fixed (https://github.com/KhronosGroup/SPIRV-Cross/pull/2163),
so it should be safe to reenable. The invalid glsl code seems to be resolved.
2024-08-22 13:08:23 -07:00
Mathias Agopian 1c2ffc9ed4 new screenshot debug option in gltf_viewer
screenshots are saved in the current directory as 
"screenshotXX.ppm" with XX increasing after each
screenshot.
2024-08-20 22:41:49 -07:00
Sungun Park 574518ea74 Cleanup color pass for multiview (#8062) 2024-08-20 21:44:17 +00:00
Ben Doherty d5a274fa25 Add better glShaderSource fix for IMG devices (#8061) 2024-08-20 12:28:13 -07:00
Mathias Agopian 3351db1178 improve FrameSkipper when disregarding skipping
It is legal for the app to draw a frame even when FrameSkipper 
detects that a frame should be skipped. In that case we used to
overwrite the most recent fence, but this wasn't ideal. We now
proceed as if the fence had signaled, i.e. we destroy it and
move all the fences up, creating a new one at the end of the 
delayed array.
2024-08-20 12:07:59 -07:00
Mathias Agopian b70aa43727 depth clamp cannot work with VSM
This is because the shadowmap doesn't store depth values so it doesn't 
work to "flatten" all casters behind de camera to the near plane.
The bulk of shadowing works but the filtering/edges don't always.

Disable depth-clamping when VSM is enabled.
2024-08-20 12:04:01 -07:00
Sungun Park 1f9a11802d Minor tweak (#8053)
Move the common part outside the loop.
2024-08-20 17:13:55 +00:00
Sungun Park 89835a7a67 Fix a bug for calculating distance (#8058)
This is a missing part from the change
22d99bac3d
2024-08-20 16:28:29 +00:00
44 changed files with 908 additions and 386 deletions
-2
View File
@@ -7,5 +7,3 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
+4
View File
@@ -7,6 +7,10 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.54.2
- Add a `name` API to Filament objects for debugging handle use-after-free assertions
## v1.54.1
+11 -2
View File
@@ -1308,7 +1308,12 @@ Description
declare a variable called `eyeDirection` you can access it in the fragment shader using
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
interpolant is of type `float4` (`vec4`) in the shaders.
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
interpolant is `highp` in *both* the vertex and fragment shaders.
An alternate syntax can be used to specify both the name and precision of the interpolant.
In this case the specified precision is used as-is in both fragment and vertex stages, in
particular if `default` is specified the default precision is used is the fragment shader
(`mediump`) and in the vertex shader (`highp`).
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
@@ -1320,7 +1325,11 @@ material {
}
],
variables : [
eyeDirection
eyeDirection,
{
name : eyeColor,
precision : medium
}
],
vertexDomain : device,
depthWrite : false,
+2
View File
@@ -160,6 +160,8 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
+2 -2
View File
@@ -53,8 +53,8 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
TrackedMetalBuffer::Type::GENERIC };
}
FILAMENT_CHECK_POSTCONDITION(mBuffer)
<< "Could not allocate Metal buffer of size " << size << ".";
// mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful().
}
MetalBuffer::~MetalBuffer() {
+3 -2
View File
@@ -57,7 +57,6 @@ class MetalDriver final : public DriverBase {
public:
static Driver* create(MetalPlatform* platform, const Platform::DriverConfig& driverConfig);
void runAtNextTick(const std::function<void()>& fn) noexcept;
private:
@@ -73,10 +72,12 @@ private:
/*
* Tasks run regularly on the driver thread.
* Not thread-safe; tasks are run from the driver thead and must be enqueued from the driver
* thread.
*/
void runAtNextTick(const std::function<void()>& fn) noexcept;
void executeTickOps() noexcept;
std::vector<std::function<void()>> mTickOps;
std::mutex mTickOpsLock;
/*
* Driver interface
+26 -6
View File
@@ -320,17 +320,40 @@ void MetalDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh,
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih) {
MetalVertexBufferInfo const* const vbi = handle_cast<const MetalVertexBufferInfo>(vbih);
construct_handle<MetalVertexBuffer>(vbh, *mContext, vertexCount, vbi->bufferCount, vbih);
// No actual GPU memory is allocated here, so no need to check for allocation success.
}
void MetalDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) {
auto elementSize = (uint8_t) getElementTypeSize(elementType);
construct_handle<MetalIndexBuffer>(ibh, *mContext, usage, elementSize, indexCount);
auto elementSize = (uint8_t)getElementTypeSize(elementType);
auto* indexBuffer =
construct_handle<MetalIndexBuffer>(ibh, *mContext, usage, elementSize, indexCount);
auto& buffer = indexBuffer->buffer;
// If the allocation was not successful, postpone the error message until the next tick, to give
// Filament a chance to call setDebugTag on the handle; this way we get a nicer error message.
if (UTILS_UNLIKELY(!buffer.wasAllocationSuccessful())) {
const size_t byteCount = buffer.getSize();
runAtNextTick([byteCount, this, ibh]() {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Could not allocate Metal index buffer of size " << byteCount
<< ", tag=" << mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe();
});
}
}
void MetalDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
construct_handle<MetalBufferObject>(boh, *mContext, bindingType, usage, byteCount);
auto* bufferObject =
construct_handle<MetalBufferObject>(boh, *mContext, bindingType, usage, byteCount);
// If the allocation was not successful, postpone the error message until the next tick, to give
// Filament a chance to call setDebugTag on the handle; this way we get a nicer error message.
if (UTILS_UNLIKELY(!bufferObject->getBuffer()->wasAllocationSuccessful())) {
runAtNextTick([byteCount, this, boh]() {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Could not allocate Metal buffer of size " << byteCount
<< ", tag=" << mHandleAllocator.getHandleTag(boh.getId()).c_str_safe();
});
}
}
void MetalDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
@@ -2066,15 +2089,12 @@ void MetalDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag)
}
void MetalDriver::runAtNextTick(const std::function<void()>& fn) noexcept {
std::lock_guard<std::mutex> const lock(mTickOpsLock);
mTickOps.push_back(fn);
}
void MetalDriver::executeTickOps() noexcept {
std::vector<std::function<void()>> ops;
mTickOpsLock.lock();
std::swap(ops, mTickOps);
mTickOpsLock.unlock();
for (const auto& f : ops) {
f();
}
@@ -257,10 +257,6 @@ void MetalSwapChain::present() {
}
}
#ifndef FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD
#define FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD 1
#endif
class PresentDrawableData {
public:
PresentDrawableData() = delete;
@@ -279,14 +275,10 @@ public:
[that->mDrawable present];
}
#if FILAMENT_RELEASE_PRESENT_DRAWABLE_MAIN_THREAD == 1
// mDrawable is acquired on the driver thread. Typically, we would release this object on
// the same thread, but after receiving consistent crash reports from within
// [CAMetalDrawable dealloc], we suspect this object requires releasing on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{ cleanupAndDestroy(that); });
#else
that->mDriver->runAtNextTick([that]() { cleanupAndDestroy(that); });
#endif
}
private:
@@ -541,9 +541,6 @@ void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
bugs->delay_fbo_destruction = true;
// PowerVR seems to have no problem with this (which is good for us)
bugs->allow_read_only_ancillary_feedback_loop = true;
// PowerVR doesn't respect lengths passed to glShaderSource, so concatenate them into a
// single string.
bugs->concatenate_shader_strings = true;
} else if (strstr(renderer, "Apple")) {
// Apple GPU
} else if (strstr(renderer, "Tegra") ||
@@ -315,12 +315,6 @@ public:
// bugs or performance issues.
bool force_feature_level0;
// Some drivers don't respect the length argument of glShaderSource() and (apparently)
// require each shader source string to be null-terminated. This works around the issue by
// concatenating the strings into a single null-terminated string before passing it to
// glShaderSource().
bool concatenate_shader_strings;
} bugs = {};
// state getters -- as needed.
@@ -567,9 +561,6 @@ private:
{ bugs.force_feature_level0,
"force_feature_level0",
""},
{ bugs.concatenate_shader_strings,
"concatenate_shader_strings",
""},
}};
// this is chosen to minimize code size
@@ -597,40 +597,30 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
version = "#version 310 es\n";
}
const std::array<const char*, 5> sources = {
version.data(),
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
std::array<std::string_view, 5> sources = {
version,
prolog,
specializationConstantString,
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
};
const std::array<GLint, 5> lengths = {
(GLint)version.length(),
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
// drivers instead interpret zero as a sentinel for a null-terminated string.
auto partitionPoint = std::stable_partition(
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
size_t count = std::distance(sources.begin(), partitionPoint);
std::array<const char*, 5> shaderStrings;
std::array<GLint, 5> lengths;
for (size_t i = 0; i < count; i++) {
shaderStrings[i] = sources[i].data();
lengths[i] = sources[i].size();
}
GLuint const shaderId = glCreateShader(glShaderType);
if (UTILS_UNLIKELY(context.bugs.concatenate_shader_strings)) {
size_t totalSize = 0;
for (size_t i = 0; i < sources.size(); i++) {
totalSize += lengths[i];
}
std::string concatenatedShaderSource;
concatenatedShaderSource.reserve(totalSize);
for (size_t i = 0; i < sources.size(); i++) {
concatenatedShaderSource.append(sources[i], lengths[i]);
}
const GLchar* ptr = concatenatedShaderSource.c_str();
GLint length = concatenatedShaderSource.length();
glShaderSource(shaderId, 1, &ptr, &length);
} else {
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
}
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
glCompileShader(shaderId);
@@ -165,14 +165,14 @@ constexpr static const int FVK_REQUIRED_VERSION_MINOR = 1;
// buffers that have been submitted but have not yet finished rendering. Note that Filament can
// issue multiple commit calls in a single frame, and that we use a triple buffered swap chain on
// some platforms.
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 10;
constexpr static const int FVK_MAX_COMMAND_BUFFERS = 3;
// Number of command buffer submissions that should occur before an unused pipeline is removed
// from the cache.
//
// If this number is low, VkPipeline construction will occur frequently, which can
// be extremely slow. If this number is high, the memory footprint will be large.
constexpr static const int FVK_MAX_PIPELINE_AGE = 10;
constexpr static const int FVK_MAX_PIPELINE_AGE = 3;
// VulkanPipelineCache does not track which command buffers contain references to which pipelines,
// instead it simply waits for at least FVK_MAX_COMMAND_BUFFERS submissions to occur before
@@ -530,6 +530,8 @@ void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, target, levels,
format, samples, w, h, depth, usage, mStagePool);
// FVK_LOGE <<"created texture: " << vktexture->getVkImage() << " dim=" <<
// vktexture->width << "x" << vktexture->height << utils::io::endl;
mResourceManager.acquire(vktexture);
}
@@ -16,7 +16,6 @@
#include "VulkanHandles.h"
#include "VulkanDriver.h"
#include "VulkanConstants.h"
#include "VulkanDriver.h"
#include "VulkanMemory.h"
+7 -10
View File
@@ -97,13 +97,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.extent.depth = 1;
}
// Filament expects blit() to work with any texture, so we almost always set these usage flags.
// TODO: investigate performance implications of setting these flags.
constexpr VkImageUsageFlags blittable = VK_IMAGE_USAGE_TRANSFER_DST_BIT |
VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::BLIT_SRC))) {
imageInfo.usage |= blittable;
if (any(usage & TextureUsage::BLIT_SRC)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
}
if (any(usage & TextureUsage::BLIT_DST)) {
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::SAMPLEABLE)) {
@@ -121,7 +119,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | blittable;
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
}
@@ -130,10 +128,9 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
}
if (any(usage & TextureUsage::UPLOADABLE)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
imageInfo.usage |= blittable;
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
// Depth resolves uses a custom shader and therefore needs to be sampleable.
+25 -18
View File
@@ -16,16 +16,22 @@
#include "FrameSkipper.h"
#include <utils/Log.h>
#include <backend/DriverEnums.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <algorithm>
#include <stddef.h>
namespace filament {
using namespace utils;
using namespace backend;
FrameSkipper::FrameSkipper(size_t latency) noexcept
: mLast(latency - 1) {
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
assert_invariant(latency <= MAX_FRAME_LATENCY);
}
@@ -41,31 +47,32 @@ void FrameSkipper::terminate(DriverApi& driver) noexcept {
bool FrameSkipper::beginFrame(DriverApi& driver) noexcept {
auto& fences = mDelayedFences;
auto fence = fences.front();
if (fence) {
auto status = driver.getFenceStatus(fence);
if (status == FenceStatus::TIMEOUT_EXPIRED) {
// Sync not ready, skip frame
if (fences.front()) {
// Do we have a latency old fence?
auto status = driver.getFenceStatus(fences.front());
if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) {
// The fence hasn't signaled yet, skip this frame
return false;
}
assert_invariant(status == FenceStatus::CONDITION_SATISFIED);
driver.destroyFence(fence);
}
// shift all fences down by 1
std::move(fences.begin() + 1, fences.end(), fences.begin());
fences.back() = {};
return true;
}
void FrameSkipper::endFrame(DriverApi& driver) noexcept {
// If the user produced a new frame despite the fact that the previous one wasn't finished
// (i.e. FrameSkipper::beginFrame() returned false), we need to make sure to replace
// a fence that might be here already)
auto& fence = mDelayedFences[mLast];
if (fence) {
driver.destroyFence(fence);
auto& fences = mDelayedFences;
size_t const last = mLast;
// pop the oldest fence and advance the other ones
if (fences.front()) {
driver.destroyFence(fences.front());
}
fence = driver.createFence();
std::move(fences.begin() + 1, fences.end(), fences.begin());
// add a new fence to the end
assert_invariant(!fences[last]);
fences[last] = driver.createFence();
}
} // namespace filament
+4 -1
View File
@@ -22,6 +22,9 @@
#include <array>
#include <stddef.h>
#include <stdint.h>
namespace filament {
/*
@@ -60,7 +63,7 @@ public:
private:
using Container = std::array<backend::Handle<backend::HwFence>, MAX_FRAME_LATENCY>;
mutable Container mDelayedFences{};
size_t mLast;
uint8_t const mLast;
};
} // namespace filament
+13 -4
View File
@@ -2945,6 +2945,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscale(FrameGraph& fg, bool
1.0f / outputDesc.height});
}
if (blitterNames[index] == "blitLow") {
mi->setParameter("levelOfDetail", 0.0f);
}
mi->setParameter("viewport", float4{
(float)vp.left / inputDesc.width,
(float)vp.bottom / inputDesc.height,
@@ -3003,9 +3007,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
SamplerMagFilter filterMag,
SamplerMinFilter filterMin) noexcept {
// TODO: add support for sub-resources
assert_invariant(fg.getSubResourceDescriptor(input).layer == 0);
assert_invariant(fg.getSubResourceDescriptor(input).level == 0);
uint32_t const layer = fg.getSubResourceDescriptor(input).layer;
float const levelOfDetail = fg.getSubResourceDescriptor(input).level;
struct QuadBlitData {
FrameGraphId<FrameGraphTexture> input;
@@ -3031,7 +3034,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
// --------------------------------------------------------------------------------
// set uniforms
PostProcessMaterial const& material = getPostProcessMaterial("blitLow");
PostProcessMaterial const& material =
getPostProcessMaterial(layer ? "blitArray" : "blitLow");
auto* mi = material.getMaterialInstance(mEngine);
mi->setParameter("color", color, {
.filterMag = filterMag,
@@ -3043,6 +3047,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool tr
float(vp.width) / inputDesc.width,
float(vp.height) / inputDesc.height
});
mi->setParameter("levelOfDetail", levelOfDetail);
if (layer) {
mi->setParameter("layerIndex", layer);
}
mi->commit(driver);
mi->use(driver);
@@ -3185,6 +3193,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
assert_invariant(dst);
assert_invariant(srcDesc.format == dstDesc.format);
assert_invariant(srcDesc.width == dstDesc.width && srcDesc.height == dstDesc.height);
assert_invariant(srcDesc.samples > 1 && dstDesc.samples <= 1);
driver.resolve(
dst, dstSubDesc.level, dstSubDesc.layer,
src, srcSubDesc.level, srcSubDesc.layer);
+1 -1
View File
@@ -610,7 +610,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
// Here, objects close to the camera (but behind) will be drawn first.
// An alternative that keeps the mathematical ordering is given here:
// distanceBits ^= ((int32_t(distanceBits) >> 31) | 0x80000000u);
float const distance = -dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward;
float const distance = -(dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward);
uint32_t const distanceBits = reinterpret_cast<uint32_t const&>(distance);
// calculate the per-primitive face winding order inversion
+3 -1
View File
@@ -528,7 +528,9 @@ public:
// like above but allows to set specific flags
RenderPassBuilder& renderFlags(
RenderPass::RenderFlags mask, RenderPass::RenderFlags value) noexcept {
mFlags = (mFlags & mask) | (value & mask);
value &= mask;
mFlags &= ~mask;
mFlags |= value;
return *this;
}
+43 -45
View File
@@ -40,6 +40,7 @@
#include <utils/Panic.h>
#include <algorithm>
#include <optional>
#include <utility>
#include <stddef.h>
@@ -50,8 +51,9 @@ namespace filament {
using namespace backend;
using namespace math;
FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
RendererUtils::ColorPassOutput RendererUtils::colorPass(
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
ColorPassInput const& colorPassInput,
FrameGraphTexture::Descriptor const& colorBufferDesc,
ColorPassConfig const& config, PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept {
@@ -67,31 +69,28 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
FrameGraphId<FrameGraphTexture> structure;
};
Blackboard& blackboard = fg.getBlackboard();
auto& colorPass = fg.addPass<ColorPassData>(name,
[&](FrameGraph::Builder& builder, ColorPassData& data) {
TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL;
uint8_t layerCount = 1;
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
data.color = blackboard.get<FrameGraphTexture>("color");
data.depth = blackboard.get<FrameGraphTexture>("depth");
data.color = colorPassInput.linearColor;
data.depth = colorPassInput.depth;
data.shadows = colorPassInput.shadows;
data.ssao = colorPassInput.ssao;
// Screen-space reflection or refractions
if (config.hasScreenSpaceReflectionsOrRefractions) {
data.ssr = blackboard.get<FrameGraphTexture>("ssr");
data.ssr = colorPassInput.ssr;
if (data.ssr) {
data.ssr = builder.sample(data.ssr);
}
}
if (config.hasContactShadows) {
data.structure = blackboard.get<FrameGraphTexture>("structure");
data.structure = colorPassInput.structure;
assert_invariant(data.structure);
data.structure = builder.sample(data.structure);
}
@@ -154,6 +153,8 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
}
if (colorGradingConfig.asSubpass) {
assert_invariant(config.msaa <= 1);
assert_invariant(colorBufferDesc.samples <= 1);
data.output = builder.createTexture("Tonemapped Buffer", {
.width = colorBufferDesc.width,
.height = colorBufferDesc.height,
@@ -172,9 +173,6 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
layerCount = engine.getConfig().stereoscopicEyeCount;
}
/*
* There is a bit of magic happening here regarding the viewport used.
@@ -196,9 +194,8 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
.stencil = data.stencil },
.clearColor = config.clearColor,
.samples = config.msaa,
.layerCount = layerCount,
.layerCount = static_cast<uint8_t>(colorBufferDesc.depth),
.clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags});
blackboard["depth"] = data.depth;
},
[=, passExecutor = std::move(passExecutor), &view, &engine](FrameGraphResources const& resources,
ColorPassData const& data, DriverApi& driver) {
@@ -264,24 +261,21 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
}
);
// when color grading is done as a subpass, the output of the color-pass is the ldr buffer
auto output = colorGradingConfig.asSubpass ? colorPass->output : colorPass->color;
blackboard["color"] = output;
return output;
return {
.linearColor = colorPass->color,
.tonemappedColor = colorPass->output, // can be null
.depth = colorPass->depth
};
}
std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
std::optional<RendererUtils::ColorPassOutput> RendererUtils::refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass const& pass) noexcept {
auto& blackboard = fg.getBlackboard();
auto input = blackboard.get<FrameGraphTexture>("color");
FrameGraphId<FrameGraphTexture> output;
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
@@ -296,34 +290,35 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
// if there wasn't any refractive object, just skip everything below.
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
PostProcessManager& ppm = engine.getPostProcessManager();
// clear the color/depth buffers, which will orphan (and cull) the color pass
input.clear();
blackboard.remove("color");
blackboard.remove("depth");
assert_invariant(!colorPassInput.linearColor);
assert_invariant(!colorPassInput.depth);
config.hasScreenSpaceReflectionsOrRefractions = true;
input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, {
PostProcessManager& ppm = engine.getPostProcessManager();
auto const opaquePassOutput = RendererUtils::colorPass(fg,
"Color Pass (opaque)", engine, view, colorPassInput, {
// When rendering the opaques, we need to conserve the sample buffer,
// so create a config that specifies the sample count.
.width = config.physicalViewport.width,
.height = config.physicalViewport.height,
.samples = config.msaa,
.format = config.hdrFormat
}, config, { .asSubpass = false },
},
config, { .asSubpass = false, .customResolve = false },
pass.getExecutor(pass.begin(), refraction));
// generate the mipmap chain
PostProcessManager::generateMipmapSSR(ppm, fg,
input, ssrConfig.refraction, true, ssrConfig);
opaquePassOutput.linearColor,
ssrConfig.refraction,
true, ssrConfig);
// Now we're doing the refraction pass proper.
// This uses the same framebuffer (color and depth) used by the opaque pass. This happens
// automatically because these are set in the Blackboard (they were set by the opaque
// pass). For this reason, `desc` below is only used in colorPass() for the width and
// height.
// This uses the same framebuffer (color and depth) used by the opaque pass.
// For this reason, the `colorBufferDesc` parameter of colorPass() below is only used for
// the width and height.
colorPassInput.linearColor = opaquePassOutput.linearColor;
colorPassInput.depth = opaquePassOutput.depth;
// Since we're reusing the existing target we don't want to clear any of its buffer.
// Important: if this target ended up being an imported target, then the clearFlags
@@ -331,22 +326,25 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
// and we'd end up clearing the opaque pass. This scenario never happens because it is
// prevented in Renderer.cpp's final blit.
config.clearFlags = TargetBufferFlags::NONE;
output = RendererUtils::colorPass(fg, "Color Pass (transparent)", engine, view, {
auto transparentPassOutput = RendererUtils::colorPass(fg, "Color Pass (transparent)",
engine, view, colorPassInput, {
.width = config.physicalViewport.width,
.height = config.physicalViewport.height },
config, colorGradingConfig, pass.getExecutor(refraction, pass.end()));
config, colorGradingConfig,
pass.getExecutor(refraction, pass.end()));
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
// We need to do a resolve here because later passes (such as color grading or DoF) will
// need to sample from 'output'. However, because we have MSAA, we know we're not
// sampleable. And this is because in the SSR case, we had to use a renderbuffer to
// conserve the multi-sample buffer.
output = ppm.resolve(fg, "Resolved Color Buffer", output, { .levels = 1 });
transparentPassOutput.linearColor = ppm.resolve(fg, "Resolved Color Buffer",
transparentPassOutput.linearColor, { .levels = 1 });
}
} else {
output = input;
return transparentPassOutput;
}
return { output, hasScreenSpaceRefraction };
return std::nullopt;
}
UTILS_NOINLINE
+24 -2
View File
@@ -21,14 +21,19 @@
#include "RenderPass.h"
#include "fg/FrameGraphId.h"
#include "fg/FrameGraphTexture.h"
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <backend/PixelBufferDescriptor.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <stdint.h>
#include <optional>
#include <utility>
namespace filament {
@@ -71,15 +76,32 @@ public:
bool screenSpaceReflectionHistoryNotReady;
};
static FrameGraphId<FrameGraphTexture> colorPass(
struct ColorPassInput {
FrameGraphId<FrameGraphTexture> linearColor;
FrameGraphId<FrameGraphTexture> tonemappedColor;
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> shadows;
FrameGraphId<FrameGraphTexture> ssao;
FrameGraphId<FrameGraphTexture> ssr;
FrameGraphId<FrameGraphTexture> structure;
};
struct ColorPassOutput {
FrameGraphId<FrameGraphTexture> linearColor;
FrameGraphId<FrameGraphTexture> tonemappedColor;
FrameGraphId<FrameGraphTexture> depth;
};
static ColorPassOutput colorPass(
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
ColorPassInput const& colorPassInput,
FrameGraphTexture::Descriptor const& colorBufferDesc,
ColorPassConfig const& config,
PostProcessManager::ColorGradingConfig colorGradingConfig,
RenderPass::Executor passExecutor) noexcept;
static std::pair<FrameGraphId<FrameGraphTexture>, bool> refractionPass(
static std::optional<RendererUtils::ColorPassOutput> refractionPass(
FrameGraph& fg, FEngine& engine, FView const& view,
ColorPassInput colorPassInput,
ColorPassConfig config,
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
PostProcessManager::ColorGradingConfig colorGradingConfig,
+8 -16
View File
@@ -28,12 +28,6 @@
#include <string_view>
#include <utility>
#ifndef NDEBUG
# define DEBUG_PROPERTIES_WRITABLE true
#else
# define DEBUG_PROPERTIES_WRITABLE false
#endif
using namespace filament::math;
using namespace utils;
@@ -79,17 +73,15 @@ bool FDebugRegistry::hasProperty(const char* name) const noexcept {
template<typename T>
bool FDebugRegistry::setProperty(const char* name, T v) noexcept {
if constexpr (DEBUG_PROPERTIES_WRITABLE) {
auto info = getPropertyInfo(name);
T* const addr = static_cast<T*>(info.first);
if (addr) {
auto old = *addr;
*addr = v;
if (info.second && old != v) {
info.second();
}
return true;
auto info = getPropertyInfo(name);
T* const addr = static_cast<T*>(info.first);
if (addr) {
auto old = *addr;
*addr = v;
if (info.second && old != v) {
info.second();
}
return true;
}
return false;
}
+18 -10
View File
@@ -192,19 +192,27 @@ Material* Material::Builder::build(Engine& engine) {
return nullptr;
}
// Print a warning if the material's stereo type doesn't align with the engine's setting.
MaterialDomain materialDomain;
UserVariantFilterMask variantFilterMask;
materialParser->getMaterialDomain(&materialDomain);
if (materialDomain == MaterialDomain::SURFACE) {
materialParser->getMaterialVariantFilterMask(&variantFilterMask);
bool const hasStereoVariants = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::STE));
if (materialDomain == MaterialDomain::SURFACE && hasStereoVariants) {
StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType;
StereoscopicType materialStereoscopicType = StereoscopicType::NONE;
materialParser->getStereoscopicType(&materialStereoscopicType);
if (materialStereoscopicType != engineStereoscopicType) {
CString name;
materialParser->getName(&name);
slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe()
<< "' is " << (int)materialStereoscopicType
<< ", which is not compatiable with the engine's setting "
<< (int)engineStereoscopicType << "." << io::endl;
// Default materials are always compiled with either 'instanced' or 'multiview'.
// So, we only verify compatibility if the engine is set up for stereo.
if (engineStereoscopicType != StereoscopicType::NONE) {
StereoscopicType materialStereoscopicType = StereoscopicType::NONE;
materialParser->getStereoscopicType(&materialStereoscopicType);
if (materialStereoscopicType != engineStereoscopicType) {
CString name;
materialParser->getName(&name);
slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe()
<< "' is " << (int)materialStereoscopicType
<< ", which is not compatiable with the engine's setting "
<< (int)engineStereoscopicType << "." << io::endl;
}
}
}
+1 -1
View File
@@ -34,7 +34,7 @@ struct RenderTarget::BuilderDetails {
uint32_t mWidth{};
uint32_t mHeight{};
uint8_t mSamples = 1; // currently not settable in the public facing API
uint8_t mLayerCount = 0;// currently not settable in the public facing API
uint8_t mLayerCount = 1;// currently not settable in the public facing API
};
using BuilderType = RenderTarget;
+48 -25
View File
@@ -655,16 +655,16 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// on qualcomm hardware -- we might need a backend dependent toggle at some point
const PostProcessManager::ColorGradingConfig colorGradingConfig{
.asSubpass =
hasColorGrading &&
msaaSampleCount <= 1 &&
!bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled &&
driver.isFrameBufferFetchSupported() &&
hasColorGrading &&
!bloomOptions.enabled && !dofOptions.enabled && !taaOptions.enabled &&
!engine.debug.renderer.disable_subpasses,
.customResolve =
msaaOptions.customResolve &&
msaaSampleCount > 1 &&
hasColorGrading &&
driver.isFrameBufferFetchMultiSampleSupported() &&
msaaOptions.customResolve &&
hasColorGrading &&
!engine.debug.renderer.disable_subpasses,
.translucent = needsAlphaChannel,
.fxaa = hasFXAA,
@@ -673,6 +673,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
TextureFormat::RGBA8 : getLdrFormat(needsAlphaChannel)
};
// by construction (msaaSampleCount) both asSubpass and customResolve can't be true
assert_invariant(colorGradingConfig.asSubpass + colorGradingConfig.customResolve < 2);
// whether we're scaled at all
bool scaled = any(notEqual(scale, float2(1.0f)));
@@ -695,13 +698,12 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
CameraInfo cameraInfo = view.computeCameraInfo(engine);
// when colorgrading-as-subpass is active, we know that many other effects are disabled
// such as dof, bloom. Moreover, if fxaa and scaling are not enabled, we're essentially in
// a very fast rendering path -- in this case, we would need an extra blit to "resolve" the
// buffer padding (because there are no other pass that can do it as a side effect).
// In this case, it is better to skip the padding, which won't be helping much.
const bool noBufferPadding = (colorGradingConfig.asSubpass && !hasFXAA && !scaled)
|| engine.debug.renderer.disable_buffer_padding;
// If fxaa and scaling are not enabled, we're essentially in a very fast rendering path -- in
// this case, we would need an extra blit to "resolve" the buffer padding (because there are no
// other pass that can do it as a side effect). In this case, it is better to skip the padding,
// which won't be helping much.
const bool noBufferPadding
= (!hasFXAA && !scaled) || engine.debug.renderer.disable_buffer_padding;
// guardBand must be a multiple of 16 to guarantee the same exact rendering up to 4 mip levels.
float const guardBand = guardBandOptions.enabled ? 16.0f : 0.0f;
@@ -952,7 +954,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
.scale = aoOptions.resolution,
.picking = view.hasPicking()
});
blackboard["structure"] = structure;
const auto picking = picking_;
@@ -1000,7 +1001,6 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
ssReflectionsOptions.enabled ? TextureFormat::RGBA16F : TextureFormat::R11F_G11F_B10F,
view.getCameraUser().getFieldOfView(Camera::Fov::VERTICAL), config.scale);
config.ssrLodOffset = ssrConfig.lodOffset;
blackboard["ssr"] = ssrConfig.ssr;
// --------------------------------------------------------------------------------------------
// screen-space reflections pass
@@ -1104,24 +1104,39 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
);
// the color pass itself + color-grading as subpass if needed
auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view,
auto colorPassOutput = RendererUtils::colorPass(fg, "Color Pass", mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
colorBufferDesc, config, colorGradingConfigForColor, pass.getExecutor());
if (view.isScreenSpaceRefractionEnabled() && !pass.empty()) {
// this cancels the colorPass() call above if refraction is active.
// the color pass + refraction + color-grading as subpass if needed
const auto [output, enabled] = RendererUtils::refractionPass(fg, mEngine, view,
// This cancels the colorPass() call above if refraction is active.
// The color pass + refraction + color-grading as subpass if needed
auto const output = RendererUtils::refractionPass(fg, mEngine, view, {
.shadows = blackboard.get<FrameGraphTexture>("shadows"),
.ssao = blackboard.get<FrameGraphTexture>("ssao"),
.ssr = ssrConfig.ssr,
.structure = structure
},
config, ssrConfig, colorGradingConfigForColor, pass);
colorPassOutput = output;
hasScreenSpaceRefraction = enabled;
hasScreenSpaceRefraction = output.has_value();
if (hasScreenSpaceRefraction) {
colorPassOutput = output.value();
}
}
if (colorGradingConfig.customResolve) {
assert_invariant(fg.getDescriptor(colorPassOutput.linearColor).samples <= 1);
// TODO: we have to "uncompress" (i.e. detonemap) the color buffer here because it's used
// by many other passes (Bloom, TAA, DoF, etc...). We could make this more
// efficient by using ARM_shader_framebuffer_fetch. We use a load/store (i.e.
// subpass) here because it's more convenient.
colorPassOutput = ppm.customResolveUncompressPass(fg, colorPassOutput);
colorPassOutput.linearColor =
ppm.customResolveUncompressPass(fg, colorPassOutput.linearColor);
}
// export the color buffer if screen-space reflections are enabled
@@ -1137,7 +1152,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// The "output" of this pass is going to be used during the next frame as
// an "import".
builder.sideEffect();
data.history = builder.sample(colorPassOutput); // FIXME: an access must be declared for detach(), why?
// we can't use colorPassOutput here because it could be tonemapped
data.history = builder.sample(colorPassOutput.linearColor); // FIXME: an access must be declared for detach(), why?
}, [&view, projection](FrameGraphResources const& resources, auto const& data,
backend::DriverApi&) {
auto& history = view.getFrameHistory();
@@ -1147,7 +1164,14 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
});
}
FrameGraphId<FrameGraphTexture> input = colorPassOutput;
// this is the output of the color pass / input to post processing,
// this is only used later for comparing it with the output after post-processing
FrameGraphId<FrameGraphTexture> const postProcessInput = colorGradingConfig.asSubpass ?
colorPassOutput.tonemappedColor :
colorPassOutput.linearColor;
// input can change below
FrameGraphId<FrameGraphTexture> input = postProcessInput;
fg.addTrivialSideEffectPass("Finish Color Passes", [&view](DriverApi& driver) {
// Unbind SSAO sampler, b/c the FrameGraph will delete the texture at the end of the pass.
view.cleanupRenderPasses();
@@ -1158,8 +1182,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// if the depth is not used below or if the depth is not MS (e.g. it could have been
// auto-resolved).
// In practice, this is used on Vulkan and older Metal devices.
auto depth = blackboard.get<FrameGraphTexture>("depth");
depth = ppm.resolve(fg, "Resolved Depth Buffer", depth, { .levels = 1 });
auto depth = ppm.resolve(fg, "Resolved Depth Buffer", colorPassOutput.depth, { .levels = 1 });
// Debug: CSM visualisation
if (UTILS_UNLIKELY(engine.debug.shadowmap.visualize_cascades &&
@@ -1275,7 +1298,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// The intermediate buffer is accomplished with a "fake" opaqueBlit (i.e. blit) operation.
const bool outputIsSwapChain =
(input == colorPassOutput) && (viewRenderTarget == mRenderTargetHandle);
(input == postProcessInput) && (viewRenderTarget == mRenderTargetHandle);
if (mightNeedFinalBlit) {
if (blendModeTranslucent ||
xvp != svp ||
+3 -3
View File
@@ -190,10 +190,10 @@ void RenderPassNode::resolve() noexcept {
minHeight = std::min(minHeight, h);
maxHeight = std::max(maxHeight, h);
}
// additionally, clear implies discardStart
rt.backend.params.flags.discardStart |= (
rt.descriptor.clearFlags & rt.targetBufferFlags);
}
// additionally, clear implies discardStart
rt.backend.params.flags.discardStart |= (
rt.descriptor.clearFlags & rt.targetBufferFlags);
assert_invariant(minWidth == maxWidth);
assert_invariant(minHeight == maxHeight);
+5 -1
View File
@@ -10,6 +10,10 @@ material {
type : int,
name : layerIndex
},
{
type : float,
name : levelOfDetail,
},
{
type : float4,
name : viewport,
@@ -33,6 +37,6 @@ vertex {
fragment {
void postProcess(inout PostProcessInputs postProcess) {
postProcess.color = textureLod(materialParams_color, vec3(variable_vertex.xy, materialParams.layerIndex), 0.0);
postProcess.color = textureLod(materialParams_color, vec3(variable_vertex.xy, materialParams.layerIndex), materialParams.levelOfDetail);
}
}
+5 -1
View File
@@ -10,6 +10,10 @@ material {
type : float4,
name : viewport,
precision: high
},
{
type : float,
name : levelOfDetail,
}
],
variables : [
@@ -33,7 +37,7 @@ fragment {
#if FILAMENT_EFFECTIVE_VERSION == 100
postProcess.color = texture2D(materialParams_color, variable_vertex.xy);
#else
postProcess.color = textureLod(materialParams_color, variable_vertex.xy, 0.0);
postProcess.color = textureLod(materialParams_color, variable_vertex.xy, materialParams.levelOfDetail);
#endif
}
}
+10 -1
View File
@@ -323,6 +323,9 @@ public:
//! Custom variables (all float4).
MaterialBuilder& variable(Variable v, const char* name) noexcept;
MaterialBuilder& variable(Variable v, const char* name,
ParameterPrecision precision) noexcept;
/**
* Require a specified attribute.
*
@@ -708,11 +711,17 @@ public:
ShaderStage stage;
};
struct CustomVariable {
utils::CString name;
Precision precision = Precision::DEFAULT;
bool hasPrecision = false;
};
static constexpr size_t MATERIAL_PROPERTIES_COUNT = filament::MATERIAL_PROPERTIES_COUNT;
using Property = filament::Property;
using PropertyList = bool[MATERIAL_PROPERTIES_COUNT];
using VariableList = utils::CString[MATERIAL_VARIABLES_COUNT];
using VariableList = CustomVariable[MATERIAL_VARIABLES_COUNT];
using OutputList = std::vector<Output>;
static constexpr size_t MAX_COLOR_OUTPUT = filament::backend::MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT;
+9 -7
View File
@@ -671,13 +671,15 @@ void GLSLPostProcessor::fixupClipDistance(
// - triggers a crash on some Adreno drivers (b/291140208, b/289401984, b/289393290)
// However Metal requires this pass in order to correctly generate half-precision MSL
//
// CreateSimplificationPass() creates a lot of problems:
// Note: CreateSimplificationPass() used to creates a lot of problems:
// - Adreno GPU show artifacts after running simplification passes (Vulkan)
// - spirv-cross fails generating working glsl
// (https://github.com/KhronosGroup/SPIRV-Cross/issues/2162)
// - generally it makes the code more complicated, e.g.: replacing for loops with
// while-if-break, unclear if it helps for anything.
// However, the simplification passes below are necessary when targeting Metal, otherwise the
//
// However this problem was addressed in spirv-cross here:
// https://github.com/KhronosGroup/SPIRV-Cross/pull/2163
//
// The simplification passes below are necessary when targeting Metal, otherwise the
// result is mismatched half / float assignments in MSL.
@@ -710,11 +712,11 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
RegisterPass(CreateAggressiveDCEPass());
RegisterPass(CreateRedundancyEliminationPass());
RegisterPass(CreateCombineAccessChainsPass());
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateVectorDCEPass());
RegisterPass(CreateDeadInsertElimPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateIfConversionPass());
RegisterPass(CreateCopyPropagateArraysPass());
RegisterPass(CreateReduceLoadSizePass());
@@ -723,7 +725,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
RegisterPass(CreateRedundancyEliminationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
RegisterPass(CreateSimplificationPass(), MaterialBuilder::TargetApi::METAL);
RegisterPass(CreateSimplificationPass());
}
void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& config) {
+16 -2
View File
@@ -234,7 +234,21 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep
case Variable::CUSTOM2:
case Variable::CUSTOM3:
assert(size_t(v) < MATERIAL_VARIABLES_COUNT);
mVariables[size_t(v)] = CString(name);
mVariables[size_t(v)] = { CString(name), Precision::DEFAULT, false };
break;
}
return *this;
}
MaterialBuilder& MaterialBuilder::variable(Variable v,
const char* name, ParameterPrecision precision) noexcept {
switch (v) {
case Variable::CUSTOM0:
case Variable::CUSTOM1:
case Variable::CUSTOM2:
case Variable::CUSTOM3:
assert(size_t(v) < MATERIAL_VARIABLES_COUNT);
mVariables[size_t(v)] = { CString(name), precision, true };
break;
}
return *this;
@@ -1383,7 +1397,7 @@ bool MaterialBuilder::checkMaterialLevelFeatures(MaterialInfo const& info) const
bool MaterialBuilder::hasCustomVaryings() const noexcept {
for (const auto& variable : mVariables) {
if (!variable.empty()) {
if (!variable.name.empty()) {
return true;
}
}
+9 -4
View File
@@ -451,15 +451,20 @@ io::sstream& CodeGenerator::generatePostProcessMain(io::sstream& out, ShaderStag
}
io::sstream& CodeGenerator::generateVariable(io::sstream& out, ShaderStage stage,
const CString& name, size_t index) {
const MaterialBuilder::CustomVariable& variable, size_t index) {
auto const& name = variable.name;
const char* precisionString = getPrecisionQualifier(variable.precision);
if (!name.empty()) {
if (stage == ShaderStage::VERTEX) {
out << "\n#define VARIABLE_CUSTOM" << index << " " << name.c_str() << "\n";
out << "\n#define VARIABLE_CUSTOM_AT" << index << " variable_" << name.c_str() << "\n";
out << "LAYOUT_LOCATION(" << index << ") VARYING vec4 variable_" << name.c_str() << ";\n";
out << "LAYOUT_LOCATION(" << index << ") VARYING " << precisionString << " vec4 variable_" << name.c_str() << ";\n";
} else if (stage == ShaderStage::FRAGMENT) {
out << "\nLAYOUT_LOCATION(" << index << ") VARYING highp vec4 variable_" << name.c_str() << ";\n";
if (!variable.hasPrecision && variable.precision == Precision::DEFAULT) {
// for backward compatibility
precisionString = "highp";
}
out << "\nLAYOUT_LOCATION(" << index << ") VARYING " << precisionString << " vec4 variable_" << name.c_str() << ";\n";
}
}
return out;
+1 -1
View File
@@ -103,7 +103,7 @@ public:
// generate declarations for custom interpolants
static utils::io::sstream& generateVariable(utils::io::sstream& out, ShaderStage stage,
const utils::CString& name, size_t index);
const MaterialBuilder::CustomVariable& variable, size_t index);
// generate declarations for non-custom "in" variables
utils::io::sstream& generateShaderInputs(utils::io::sstream& out, ShaderStage type,
+7 -1
View File
@@ -56,7 +56,13 @@ bool operator==(const MaterialKey& k1, const MaterialKey& k2) {
(k1.volumeThicknessUV == k2.volumeThicknessUV) &&
(k1.hasSheen == k2.hasSheen) &&
(k1.hasIOR == k2.hasIOR) &&
(k1.hasVolume == k2.hasVolume);
(k1.hasVolume == k2.hasVolume) &&
(k1.hasSpecular == k2.hasSpecular) &&
(k1.hasSpecularTexture == k2.hasSpecularTexture) &&
(k1.hasSpecularColorTexture == k2.hasSpecularColorTexture) &&
(k1.specularTextureUV == k2.specularTextureUV) &&
(k1.specularColorTextureUV == k2.specularColorTextureUV)
;
}
// Filament supports up to 2 UV sets. glTF has arbitrary texcoord set indices, but it allows
+28 -2
View File
@@ -259,6 +259,24 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config
mi->setParameter("occlusionUvMatrix", identity);
mi->setParameter("emissiveUvMatrix", identity);
// set to -1 all possibly optional parameters
for (auto const* param : {
"clearCoatIndex",
"clearCoatRoughnessIndex",
"clearCoatNormalIndex",
"sheenColorIndex",
"sheenRoughnessIndex",
"volumeThicknessUvMatrix",
"volumeThicknessIndex",
"transmissionIndex",
"specularIndex",
"specularColorIndex",
}) {
if (material->hasParameter(param)) {
mi->setParameter(param, -1);
}
}
if (config->hasClearCoat) {
mi->setParameter("clearCoatIndex",
getUvIndex(config->clearCoatUV, config->hasClearCoatTexture));
@@ -340,10 +358,10 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config
mi->setParameter("sheenRoughnessMap", mDummyTexture, sampler);
}
if (mi->getMaterial()->hasParameter("ior")) {
if (material->hasParameter("ior")) {
mi->setParameter("ior", 1.5f);
}
if (mi->getMaterial()->hasParameter("reflectance")) {
if (material->hasParameter("reflectance")) {
mi->setParameter("reflectance", 0.5f);
}
@@ -355,6 +373,14 @@ MaterialInstance* UbershaderProvider::createMaterialInstance(MaterialKey* config
mi->setParameter("specularColorMap", mDummyTexture, sampler);
}
if (material->hasParameter("specularColorFactor")) {
mi->setParameter("specularColorFactor", float3(1.0f));
}
if (material->hasParameter("specularStrength")) {
mi->setParameter("specularStrength", 1.0f);
}
return mi;
}
@@ -527,11 +527,13 @@ bool AssetLoaderExtended::createPrimitive(Input* input, Output* out,
if (!mCgltfBuffersLoaded) {
mCgltfBuffersLoaded = utility::loadCgltfBuffers(gltf, mGltfPath.c_str(), mUriDataCache);
if (!mCgltfBuffersLoaded) return false;
if (!mCgltfBuffersLoaded) {
return false;
}
utility::decodeMeshoptCompression(gltf);
}
utility::decodeDracoMeshes(gltf, prim, input->dracoCache);
utility::decodeMeshoptCompression(gltf);
auto slots = computeGeometries(prim, jobType, attributesMap, morphTargets, out->uvmap, mEngine);
@@ -224,6 +224,9 @@ public:
*/
static void exportSettings(const Settings& settings, const char* filename);
static void exportScreenshot(View* view, Renderer* renderer, std::string filename,
bool autoclose, AutomationEngine* automationEngine);
Options getOptions() const { return mOptions; }
bool isRunning() const { return mIsRunning; }
size_t currentTest() const { return mCurrentTest; }
+3 -2
View File
@@ -56,7 +56,7 @@ static void convertRGBAtoRGB(void* buffer, uint32_t width, uint32_t height) {
}
}
static void exportScreenshot(View* view, Renderer* renderer, std::string filename,
void AutomationEngine::exportScreenshot(View* view, Renderer* renderer, std::string filename,
bool autoclose, AutomationEngine* automationEngine) {
const Viewport& vp = view->getViewport();
const size_t byteCount = vp.width * vp.height * 4;
@@ -244,7 +244,8 @@ void AutomationEngine::tick(Engine* engine, const ViewerContent& content, float
}
if (mOptions.exportScreenshots) {
exportScreenshot(content.view, content.renderer, prefix + ".ppm", isLastTest, this);
AutomationEngine::exportScreenshot(
content.view, content.renderer, prefix + ".ppm", isLastTest, this);
}
if (isLastTest) {
+2
View File
@@ -39,6 +39,7 @@ set(MATERIAL_SRCS
materials/sandboxSubsurface.mat
materials/sandboxUnlit.mat
materials/texturedLit.mat
materials/iconTest.mat
)
if (CMAKE_CROSSCOMPILING)
@@ -270,6 +271,7 @@ if (NOT ANDROID)
add_demo(viewtest)
# Sample app specific
target_link_libraries(hellotriangle PRIVATE gltf-demo-resources uberarchive gltfio)
target_link_libraries(frame_generator PRIVATE imageio)
target_link_libraries(gltf_viewer PRIVATE gltf-demo-resources uberarchive gltfio viewer)
target_link_libraries(gltf_instances PRIVATE gltf-demo-resources uberarchive gltfio viewer)
+21 -4
View File
@@ -61,8 +61,10 @@
#include <algorithm>
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include "generated/resources/gltf_demo.h"
@@ -137,6 +139,8 @@ struct App {
AutomationSpec* automationSpec = nullptr;
AutomationEngine* automationEngine = nullptr;
bool screenshot = false;
uint8_t screenshotSeq = 0;
};
static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b";
@@ -877,10 +881,15 @@ int main(int argc, char** argv) {
if (ImGui::CollapsingHeader("Debug")) {
auto& debug = engine->getDebugRegistry();
if (ImGui::Button("Capture frame")) {
bool* captureFrame =
debug.getPropertyAddress<bool>("d.renderer.doFrameCapture");
*captureFrame = true;
if (engine->getBackend() == Engine::Backend::METAL) {
if (ImGui::Button("Capture frame")) {
bool* captureFrame =
debug.getPropertyAddress<bool>("d.renderer.doFrameCapture");
*captureFrame = true;
}
}
if (ImGui::Button("Screenshot")) {
app.screenshot = true;
}
ImGui::Checkbox("Disable buffer padding",
debug.getPropertyAddress<bool>("d.renderer.disable_buffer_padding"));
@@ -1136,6 +1145,14 @@ int main(int argc, char** argv) {
};
auto postRender = [&app](Engine* engine, View* view, Scene*, Renderer* renderer) {
if (app.screenshot) {
std::ostringstream stringStream;
stringStream << "screenshot" << std::setfill('0') << std::setw(2) << +app.screenshotSeq;
AutomationEngine::exportScreenshot(
view, renderer, stringStream.str() + ".ppm", false, app.automationEngine);
++app.screenshotSeq;
app.screenshot = false;
}
if (app.automationEngine->shouldClose()) {
FilamentApp::get().close();
return;
+449 -151
View File
@@ -1,192 +1,490 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fstream>
#include <iostream>
#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/LightManager.h>
#include <filament/RenderableManager.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <filament/SwapChain.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <filament/RenderTarget.h>
#include <filamat/MaterialBuilder.h>
#include <utils/EntityManager.h>
#include <utils/Log.h>
#include <filamentapp/Config.h>
#include <filamentapp/FilamentApp.h>
#include <math/norm.h>
#include <getopt/getopt.h>
#include <gltfio/AssetLoader.h>
#include <gltfio/ResourceLoader.h>
#include <cmath>
#include <iostream>
#include <SDL.h>
#include <SDL_syswm.h>
#include <SDL_video.h>
#include <filamentapp/NativeWindowHelper.h>
#include "generated/resources/resources.h"
#include "materials/uberarchive.h"
#include <vector>
using namespace filament;
using utils::Entity;
using utils::EntityManager;
using namespace math;
using namespace utils;
struct App {
Config config;
VertexBuffer* vb;
IndexBuffer* ib;
Material* mat;
Camera* cam;
Entity camera;
Skybox* skybox;
Entity renderable;
namespace {
const static uint32_t gIndices[] = {0, 1, 2, 2, 3, 0};
float const QUAD_WIDTH = .5;
const static math::float3 gVertices[] = {
float3 {0, 0, 0} * QUAD_WIDTH,
float3 {0, 1, 0} * QUAD_WIDTH,
float3 {1, 1, 0} * QUAD_WIDTH,
float3 {1, 0, 0} * QUAD_WIDTH,
};
struct Vertex {
filament::math::float2 position;
uint32_t color;
const static math::float2 gUVs[] = {
{0, 0},
{0, 1},
{1, 1},
{1, 0},
};
static const Vertex TRIANGLE_VERTICES[3] = {
{{1, 0}, 0xffff0000u},
{{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u},
{{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu},
};
const static short4 tbn = math::packSnorm16(mat3f::packTangentFrame(
math::mat3f{float3{1.0f, 0.0f, 0.0f}, float3{0.0f, 0.0f, 1.0f},
float3{0.0f, 1.0f, 0.0f}}).xyzw);
static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };
const static math::short4 gNormals[]{tbn, tbn, tbn, tbn};
static void printUsage(char* name) {
std::string exec_name(utils::Path(name).getName());
std::string usage(
"HELLOTRIANGLE renders a spinning colored triangle\n"
"Usage:\n"
" HELLOTRIANGLE [options]\n"
"Options:\n"
" --help, -h\n"
" Prints this message\n\n"
" --api, -a\n"
" Specify the backend API: opengl, vulkan, or metal\n"
);
const std::string from("HELLOTRIANGLE");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), exec_name);
struct TestPrimitive {
TestPrimitive(Engine* engine) {
vertexBuffer =
VertexBuffer::Builder()
.vertexCount(4)
.bufferCount(3)
.attribute(VertexAttribute::POSITION, 0,
VertexBuffer::AttributeType::FLOAT3)
.attribute(VertexAttribute::UV0, 1, VertexBuffer::AttributeType::FLOAT2)
.attribute(VertexAttribute::TANGENTS, 2, VertexBuffer::AttributeType::FLOAT4)
.build(*engine);
vertexBuffer->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(gVertices,
vertexBuffer->getVertexCount() * sizeof(gVertices[0])));
vertexBuffer->setBufferAt(*engine, 1,
VertexBuffer::BufferDescriptor(gUVs,
vertexBuffer->getVertexCount() * sizeof(gUVs[0])));
vertexBuffer->setBufferAt(*engine, 2,
VertexBuffer::BufferDescriptor(gNormals,
vertexBuffer->getVertexCount() * sizeof(gNormals[0])));
indexBuffer = IndexBuffer::Builder().indexCount(6).build(*engine);
indexBuffer->setBuffer(*engine, IndexBuffer::BufferDescriptor(gIndices,
indexBuffer->getIndexCount() * sizeof(uint32_t)));
}
std::cout << usage;
VertexBuffer* verts() const { return vertexBuffer; }
IndexBuffer* inds() const { return indexBuffer; }
private:
VertexBuffer* vertexBuffer;
IndexBuffer* indexBuffer;
};
struct TestMaterial {
TestMaterial(Engine* engine) {
material = Material::Builder()
.package(RESOURCES_ICONTEST_DATA, RESOURCES_ICONTEST_SIZE)
.build(*engine);
}
MaterialInstance* instance() {
auto inst = material->createInstance();
instances.push_back(inst);
return inst;
}
private:
Material* material;
std::vector<MaterialInstance*> instances;
};
struct TestTexture {
TestTexture(Engine* engine, size_t width, size_t height, bool attachment=false) {
auto usage = Texture::Usage::DEFAULT;
if (attachment) {
usage = usage | Texture::Usage::COLOR_ATTACHMENT;
}
texture = Texture::Builder()
.width(width).height(height).levels(1)
.usage(usage)
.format(Texture::InternalFormat::RGBA8).build(*engine);
uint8_t* data = (uint8_t*) malloc(width * height * 4);
std::memset(data, 0xFF, width * height * 4);
Texture::PixelBufferDescriptor buffer(data, size_t(width * height * 4),
Texture::Format::RGBA, Texture::Type::UBYTE,
[](void* buf, size_t size, void* user) {
free(buf);
});
texture->setImage(*engine, 0, 0, 0, 0, width, height, 1, std::move(buffer));
}
Texture* get() const { return texture; }
private:
Texture* texture;
};
std::pair<SDL_Window*, void*> createSDLwindow() {
const uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
SDL_Window* win = SDL_CreateWindow("Hello World!", 0, 100, 800, 800, windowFlags);
if (win == nullptr) {
std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
SDL_Quit();
return {NULL, NULL};
}
void* nativeWindow = getNativeWindow(win);
#if defined(__APPLE__)
prepareNativeWindow(win);
setUpMetalLayer(nativeWindow);
#endif
return {win, nativeWindow};
}
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
static constexpr const char* OPTSTR = "ha:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
{ nullptr, 0, nullptr, 0 }
struct IconScene {
struct Option {
float border=1.4;
bool postProcessing=true;
bool useSkybox=false;
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
case 'a':
if (arg == "opengl") {
app->config.backend = Engine::Backend::OPENGL;
} else if (arg == "vulkan") {
app->config.backend = Engine::Backend::VULKAN;
} else if (arg == "metal") {
app->config.backend = Engine::Backend::METAL;
} else {
std::cerr << "Unrecognized backend. Must be 'opengl'|'vulkan'|'metal'.\n";
exit(1);
}
break;
IconScene(Engine* engine, Renderer* renderer, uint32_t w, uint32_t h, Option option)
: engine(engine),
renderer(renderer),
material(engine),
primitives(engine) {
cameraEntity = EntityManager::get().create();
camera = engine->createCamera(cameraEntity);
camera->lookAt(float3(0, 0, -10.f), float3(0, 0, 0), float3(0, 1.0f, 0));
float halfWidth = (QUAD_WIDTH / 2) * option.border;
camera->setProjection(Camera::Projection::ORTHO,
-halfWidth , halfWidth, -halfWidth, halfWidth, -50, 50);
scene = engine->createScene();
view = engine->createView();
view->setViewport({0, 0, w, h});
view->setScene(scene);
view->setCamera(camera);
view->setPostProcessingEnabled(false);
Entity renderable = EntityManager::get().create();
matInstance = material.instance();
RenderableManager::Builder(1)
.boundingBox({{-10, -10, -10}, {10, 10, 10}})
.material(0, matInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
primitives.verts(), primitives.inds(), 0, 6)
.culling(false)
.build(*engine, renderable);
scene->addEntity(renderable);
auto& tcm = engine->getTransformManager();
tcm.create(renderable);
auto inst = tcm.getInstance(renderable);
mat4f xlate = mat4f::translation(float3(QUAD_WIDTH / -2.0, QUAD_WIDTH / -2.0, 0));
tcm.setTransform(inst, xlate);
}
void render(Texture* texture, Texture* renderTargetTexture, float3 const& multiplier) {
TextureSampler sampler{TextureSampler::MinFilter::NEAREST,
TextureSampler::MagFilter::NEAREST};
matInstance->setParameter("tex", texture, sampler);
matInstance->setParameter("multiplier", multiplier);
RenderTarget* target;
if (auto itr = targets.find(renderTargetTexture); itr != targets.end()) {
target = itr->second;
} else {
target = RenderTarget::Builder()
.texture(RenderTarget::AttachmentPoint::COLOR, renderTargetTexture)
.build(*engine);
targets[renderTargetTexture] = target;
}
view->setRenderTarget(target);
renderer->renderStandaloneView(view);
}
void renderToSwapChain(SwapChain* swapChain, Texture* texture) {
TextureSampler sampler{TextureSampler::MinFilter::NEAREST,
TextureSampler::MagFilter::NEAREST};
matInstance->setParameter("tex", texture, sampler);
matInstance->setParameter("multiplier", float3 {1});
if (renderer->beginFrame(swapChain)) {
// for each View
renderer->render(view);
renderer->endFrame();
}
}
return optind;
private:
Engine* engine;
Renderer* renderer;
View* view;
Scene* scene;
Camera* camera;
TestMaterial material;
TestPrimitive primitives;
Entity cameraEntity;
Skybox* skybox;
MaterialInstance* matInstance;
std::unordered_map<Texture*, RenderTarget*> targets;
};
struct MainScene {
using TextureMatrix = std::vector<std::vector<Texture*>>;
MainScene(Engine* engine, Renderer* renderer, uint32_t w, uint32_t h, uint32_t nrows,
uint32_t ncols)
: renderer(renderer),
primitives{engine},
material(engine),
engine(engine) {
cameraEntity = EntityManager::get().create();
camera = engine->createCamera(cameraEntity);
camera->lookAt(float3(0, 0, -10.f), float3(0, 0, 0), float3(0, 1.0f, 0));
camera->setProjection(45.0, double(w) / h, 0.1, 50, Camera::Fov::VERTICAL);
scene = engine->createScene();
// skybox = Skybox::Builder().color({0.2, 0.2, 0.2, 1.0}).build(*engine);
// scene->setSkybox(skybox);
view = engine->createView();
view->setViewport({0, 0, w, h});
view->setScene(scene);
view->setCamera(camera);
view->setPostProcessingEnabled(false);
root = EntityManager::get().create();
auto& tcm = engine->getTransformManager();
tcm.create(root);
auto rootInst = tcm.getInstance(root);
mat4f xlate = mat4f::translation(float3(-QUAD_WIDTH / 2 * nrows , -QUAD_WIDTH / 2 * ncols, 0));
tcm.setTransform(rootInst, xlate);
matInst = material.instance();
renderable = EntityManager::get().create();
scene->addEntity(renderable);
RenderableManager::Builder(1)
.boundingBox({{-10, -10, -10}, {10, 10, 10}})
.material(0, matInst)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
primitives.verts(), primitives.inds(), 0, 6)
.culling(false)
.build(*engine, renderable);
auto transformInst = tcm.getInstance(renderable);
tcm.setParent(transformInst, rootInst);
}
void render(Texture* texture, RenderTarget* target, uint32_t r, uint32_t c) {
auto& tcm = engine->getTransformManager();
TextureSampler sampler{TextureSampler::MinFilter::NEAREST,
TextureSampler::MagFilter::NEAREST};
matInst->setParameter("tex", texture, sampler);
matInst->setParameter("multiplier", float3 {1});
auto transformInst = tcm.getInstance(renderable);
mat4f xlate = mat4f::translation(float3(QUAD_WIDTH * r, QUAD_WIDTH * c, 0));
tcm.setTransform(transformInst, xlate);
view->setRenderTarget(target);
renderer->renderStandaloneView(view);
}
void renderToSwapChain(SwapChain* swapChain, Texture* texture, uint32_t r, uint32_t c){
auto& tcm = engine->getTransformManager();
TextureSampler sampler{TextureSampler::MinFilter::NEAREST,
TextureSampler::MagFilter::NEAREST};
matInst->setParameter("tex", texture, sampler);
matInst->setParameter("multiplier", float3 {1});
auto transformInst = tcm.getInstance(renderable);
mat4f xlate = mat4f::translation(float3(QUAD_WIDTH * r, QUAD_WIDTH * c, 0));
tcm.setTransform(transformInst, xlate);
if (renderer->beginFrame(swapChain)) {
// for each View
renderer->render(view);
renderer->endFrame();
}
}
Scene* get() const { return scene; }
Camera* getCamera() const { return camera; }
View* getView() const { return view; }
private:
Renderer* renderer;
TestMaterial material;
MaterialInstance* matInst;
TestPrimitive primitives;
TextureMatrix textures;
Scene* scene;
Entity light;
Entity root;
Entity renderable;
Camera* camera;
Entity cameraEntity;
Skybox* skybox;
View* view;
std::vector<Entity> renderables;
Engine* engine;
};
}
int main(int argc, char** argv) {
App app{};
app.config.title = "hellotriangle";
app.config.featureLevel = backend::FeatureLevel::FEATURE_LEVEL_0;
handleCommandLineArguments(argc, argv, &app);
int main() {
SDL_Init(SDL_INIT_EVERYTHING);
auto const [window, nativeWindow] = createSDLwindow();
auto setup = [&app](Engine* engine, View* view, Scene* scene) {
app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
scene->setSkybox(app.skybox);
view->setPostProcessingEnabled(false);
static_assert(sizeof(Vertex) == 12, "Strange vertex size.");
app.vb = VertexBuffer::Builder()
.vertexCount(3)
.bufferCount(1)
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12)
.normalized(VertexAttribute::COLOR)
.build(*engine);
app.vb->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));
app.ib = IndexBuffer::Builder()
.indexCount(3)
.bufferType(IndexBuffer::IndexType::USHORT)
.build(*engine);
app.ib->setBuffer(*engine,
IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));
app.mat = Material::Builder()
.package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
.build(*engine);
app.renderable = EntityManager::get().create();
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.material(0, app.mat->getDefaultInstance())
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3)
.culling(false)
.receiveShadows(false)
.castShadows(false)
.build(*engine, app.renderable);
scene->addEntity(app.renderable);
app.camera = utils::EntityManager::get().create();
app.cam = engine->createCamera(app.camera);
view->setCamera(app.cam);
Engine* engine = Engine::create(filament::backend::Backend::VULKAN);
SwapChain* swapChain =
engine->createSwapChain(nativeWindow, filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
Renderer* renderer = engine->createRenderer();
// Determine the current size of the window in physical pixels.
uint32_t w, h;
SDL_GL_GetDrawableSize(window, (int*) &w, (int*) &h);
filamat::MaterialBuilder::init();
TestTexture matTexture {engine, 27, 27};
size_t const nrow = 10, ncol = 10;
uint32_t const iconWidth = 50;
uint32_t const iconHeight = 50;
IconScene iconScene {engine, renderer, iconWidth, iconHeight, {.useSkybox=false} };
auto buildTargets = [nrow, ncol, engine]() {
std::vector<std::vector<Texture*>> targets;
for (size_t r = 0; r < nrow; r++) {
std::vector<Texture*> row;
for (size_t c = 0; c < ncol; c++) {
TestTexture ttex {engine, iconWidth, iconHeight, true };
row.push_back(ttex.get());
}
targets.push_back(row);
}
return targets;
};
auto cleanup = [&app](Engine* engine, View*, Scene*) {
engine->destroy(app.skybox);
engine->destroy(app.renderable);
engine->destroy(app.mat);
engine->destroy(app.vb);
engine->destroy(app.ib);
engine->destroyCameraComponent(app.camera);
utils::EntityManager::get().destroy(app.camera);
};
std::vector<std::vector<Texture*>> targets0 = buildTargets();
std::vector<std::vector<Texture*>> targets1 = buildTargets();
std::vector<std::vector<Texture*>> targets2 = buildTargets();
FilamentApp::get().animate([&app](Engine* engine, View* view, double now) {
constexpr float ZOOM = 1.5f;
const uint32_t w = view->getViewport().width;
const uint32_t h = view->getViewport().height;
const float aspect = (float) w / h;
app.cam->setProjection(Camera::Projection::ORTHO,
-aspect * ZOOM, aspect * ZOOM,
-ZOOM, ZOOM, 0, 1);
auto& tcm = engine->getTransformManager();
tcm.setTransform(tcm.getInstance(app.renderable),
filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 }));
});
MainScene scene(engine, renderer, w, h, nrow, ncol);
FilamentApp::get().run(app.config, setup, cleanup);
renderer->setClearOptions({.clear = false});
float3 const a {.7, .3, .3};
float3 const b {.3, .7, .3};
std::vector<std::pair<size_t, size_t>> coords;
for (size_t r = 0; r < nrow; r++) {
for (size_t c = 0; c < ncol; c++) {
coords.push_back({r, c});
}
}
IconScene swapChainScene{engine, renderer, w, h, {
.border = 1.0f, .postProcessing = false}};
TestTexture mainTex{engine, w, h, true};
auto mainRT = RenderTarget::Builder()
.texture(RenderTarget::AttachmentPoint::COLOR, mainTex.get())
.build(*engine);
bool closeWindow = false;
size_t count = 0;
while (!closeWindow && ++count) {
constexpr int kMaxEvents = 16;
SDL_Event events[kMaxEvents];
int nevents = 0;
while (nevents < kMaxEvents && SDL_PollEvent(&events[nevents]) != 0) {
nevents++;
}
// Now, loop over the events a second time for app-side processing.
for (int i = 0; i < nevents; i++) {
const SDL_Event& event = events[i];
switch (event.type) {
case SDL_QUIT:
closeWindow = true;
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
break;
default:
break;
}
break;
default:
break;
}
}
auto [r, c] = coords[count % coords.size()];
float3 color0 = (r + c) % 2 == 0 ? a : b;
float3 color1 = (r + c) % 2 == 0 ? b : a;
auto rt0 = targets0[r][c];
auto rt1 = targets1[r][c];
auto rt2 = targets2[r][c];
iconScene.render(matTexture.get(), rt0, color0);
iconScene.render(rt0, rt1, color1);
iconScene.render(rt1, rt2, float3 {.8});
// engine->flushAndWait();
scene.render(rt2, mainRT, r, c);
// engine->flushAndWait();
// engine->flush();
// scene.renderToSwapChain(swapChain, rt1, r, c);
if (count % 27 == 0) {
swapChainScene.renderToSwapChain(swapChain, mainTex.get());
}
SDL_Delay(16);
// if (renderer->beginFrame(swapChain)) {
// // for each View
// renderer->render(scene.getView());
// renderer->endFrame();
// }
// SDL_Delay(16);
}
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
material {
name : Icon,
shadingModel : unlit,
requires : [
uv0
],
parameters : [
{
type : sampler2d,
name : tex
},
{
type : float3,
name : multiplier
}
],
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
vec3 m = materialParams.multiplier;
vec3 rgb = texture(materialParams_tex, getUV0()).rgb * m;
material.baseColor = vec4(rgb, 1.0);
}
}
+40 -3
View File
@@ -615,14 +615,51 @@ static bool processVariables(MaterialBuilder& builder, const JsonishValue& value
}
for (size_t i = 0; i < elements.size(); i++) {
auto elementValue = elements[i];
ParameterPrecision precision = ParameterPrecision::DEFAULT;
MaterialBuilder::Variable v = intToVariable(i);
if (elementValue->getType() != JsonishValue::Type::STRING) {
std::string nameString;
auto elementValue = elements[i];
if (elementValue->getType() == JsonishValue::Type::OBJECT) {
JsonishObject const& jsonObject = *elementValue->toJsonObject();
const JsonishValue* nameValue = jsonObject.getValue("name");
if (!nameValue) {
std::cerr << "variables: entry without 'name' key." << std::endl;
return false;
}
if (nameValue->getType() != JsonishValue::STRING) {
std::cerr << "variables: name value must be STRING." << std::endl;
return false;
}
const JsonishValue* precisionValue = jsonObject.getValue("precision");
if (precisionValue) {
if (precisionValue->getType() != JsonishValue::STRING) {
std::cerr << "variables: precision must be a STRING." << std::endl;
return false;
}
auto precisionString = precisionValue->toJsonString();
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
return logEnumIssue("variables", *precisionString, Enums::map<ParameterPrecision>());
}
}
nameString = nameValue->toJsonString()->getString();
if (precisionValue) {
precision = Enums::toEnum<ParameterPrecision>(
precisionValue->toJsonString()->getString());
}
builder.variable(v, nameString.c_str(), precision);
} else if (elementValue->getType() == JsonishValue::Type::STRING) {
nameString = elementValue->toJsonString()->getString();
builder.variable(v, nameString.c_str());
} else {
std::cerr << "variables: array index " << i << " is not a STRING. found:" <<
JsonishValue::typeToString(elementValue->getType()) << std::endl;
return false;
}
builder.variable(v, elementValue->toJsonString()->getString().c_str());
}
return true;