Compare commits
1 Commits
v1.57.2
...
pf/test-si
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d064e2ed78 |
@@ -528,7 +528,7 @@ if (FILAMENT_SUPPORTS_METAL)
|
||||
endif()
|
||||
|
||||
# Building filamat increases build times and isn't required for web, so turn it off by default.
|
||||
if (NOT WEBGL AND NOT IOS)
|
||||
if (NOT WEBGL)
|
||||
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" ON)
|
||||
else()
|
||||
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" OFF)
|
||||
|
||||
@@ -7,3 +7,5 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Support including PlatformMetal.h in C++ files.
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.57.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.57.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.57.2'
|
||||
pod 'Filament', '~> 1.57.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,10 +7,6 @@ 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.57.2
|
||||
|
||||
- Support including PlatformMetal.h in C++ files.
|
||||
|
||||
## v1.57.1
|
||||
|
||||
|
||||
|
||||
@@ -53,14 +53,6 @@ Java_com_google_android_filament_Texture_nIsTextureFormatSupported(JNIEnv*, jcla
|
||||
(Texture::InternalFormat) internalFormat);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Texture_nIsTextureFormatMipmappable(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jint internalFormat) {
|
||||
Engine *engine = (Engine *) nativeEngine;
|
||||
return (jboolean) Texture::isTextureFormatMipmappable(*engine,
|
||||
(Texture::InternalFormat) internalFormat);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Texture_nIsTextureSwizzleSupported(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
@@ -68,16 +60,6 @@ Java_com_google_android_filament_Texture_nIsTextureSwizzleSupported(JNIEnv*, jcl
|
||||
return (jboolean) Texture::isTextureSwizzleSupported(*engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Texture_nValidatePixelFormatAndType(JNIEnv*, jclass,
|
||||
jint internalFormat, jint pixelDataFormat, jint pixelDataType) {
|
||||
return (jboolean) Texture::validatePixelFormatAndType(
|
||||
(Texture::InternalFormat) internalFormat,
|
||||
(Texture::Format) pixelDataFormat,
|
||||
(Texture::Type) pixelDataType
|
||||
);
|
||||
}
|
||||
|
||||
// Texture::Builder...
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
|
||||
@@ -653,19 +653,6 @@ public class Texture {
|
||||
return nIsTextureFormatSupported(engine.getNativeObject(), format.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given texture format is supported for mipmapping in this {@link Engine}.
|
||||
* This depends on the selected backend.
|
||||
*
|
||||
* @param engine {@link Engine} to test the {@link InternalFormat InternalFormat} against
|
||||
* @param format format to check
|
||||
* @return <code>true</code> if this format is supported for texturing.
|
||||
*/
|
||||
public static boolean isTextureFormatMipmappable(@NonNull Engine engine,
|
||||
@NonNull InternalFormat format) {
|
||||
return nIsTextureFormatMipmappable(engine.getNativeObject(), format.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether texture swizzling is supported in this {@link Engine}.
|
||||
* This depends on the selected backend.
|
||||
@@ -677,20 +664,6 @@ public class Texture {
|
||||
return nIsTextureSwizzleSupported(engine.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given combination of texture format, pixel data and type is valid.
|
||||
*
|
||||
* @param internalFormat texture format
|
||||
* @param pixelDataFormat pixel data format
|
||||
* @param pixelDataType pixel data type
|
||||
* @return <code>true</code> if the combination is valid
|
||||
*/
|
||||
public static boolean validatePixelFormatAndType(@NonNull InternalFormat internalFormat,
|
||||
@NonNull Format pixelDataFormat, @NonNull Type pixelDataType) {
|
||||
return nValidatePixelFormatAndType(internalFormat.ordinal(), pixelDataFormat.ordinal(),
|
||||
pixelDataType.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use <code>Builder</code> to construct a <code>Texture</code> object instance.
|
||||
*/
|
||||
@@ -1287,12 +1260,8 @@ public class Texture {
|
||||
}
|
||||
|
||||
private static native boolean nIsTextureFormatSupported(long nativeEngine, int internalFormat);
|
||||
private static native boolean nIsTextureFormatMipmappable(long nativeEngine, int internalFormat);
|
||||
private static native boolean nIsTextureSwizzleSupported(long nativeEngine);
|
||||
|
||||
private static native boolean nValidatePixelFormatAndType(int internalFormat, int pixelDataFormat,
|
||||
int pixelDataType);
|
||||
|
||||
private static native long nCreateBuilder();
|
||||
private static native void nDestroyBuilder(long nativeBuilder);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.57.2
|
||||
VERSION_NAME=1.57.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -460,8 +460,7 @@ if (APPLE OR LINUX)
|
||||
spirv-cross-glsl)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
if (APPLE)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
|
||||
@@ -144,13 +144,6 @@ public:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired. If true, the
|
||||
* frame is aborted instead of panic. This is only supported for:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
// Set to true to print every command out on log.d. This requires RTTI and DEBUG
|
||||
#define DEBUG_COMMAND_STREAM false
|
||||
#define DEBUG_COMMAND_STREAM true
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
@@ -250,8 +249,8 @@ public:
|
||||
if (UTILS_LIKELY(isPoolHandle(id))) {
|
||||
// Truncate the age to get the debug tag
|
||||
key &= ~(HANDLE_DEBUG_TAG_MASK ^ HANDLE_AGE_MASK);
|
||||
writeHandleTag(key, std::move(tag));
|
||||
}
|
||||
writeHandleTag(key, std::move(tag));
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -427,7 +426,7 @@ private:
|
||||
// Below is only used when running out of space in the HandleArena
|
||||
mutable utils::Mutex mLock;
|
||||
tsl::robin_map<HandleBase::HandleId, void*> mOverflowMap;
|
||||
std::atomic<HandleBase::HandleId> mId = 0;
|
||||
HandleBase::HandleId mId = 0;
|
||||
bool mUseAfterFreeCheckDisabled = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ MetalDriver::MetalDriver(
|
||||
device.device = nil;
|
||||
mPlatform.createDevice(device);
|
||||
mContext->device = device.device;
|
||||
FILAMENT_CHECK_POSTCONDITION(mContext->device) << "Could not obtain Metal device.";
|
||||
assert_invariant(mContext->device);
|
||||
|
||||
mContext->emptyBuffer = [mContext->device newBufferWithLength:16
|
||||
options:MTLResourceStorageModePrivate];
|
||||
@@ -193,8 +193,7 @@ MetalDriver::MetalDriver(
|
||||
MetalCommandQueue commandQueue;
|
||||
commandQueue.commandQueue = nil;
|
||||
mPlatform.createCommandQueue(device, commandQueue);
|
||||
FILAMENT_CHECK_POSTCONDITION(commandQueue.commandQueue)
|
||||
<< "Could not create Metal command queue.";
|
||||
assert_invariant(commandQueue.commandQueue);
|
||||
|
||||
mContext->commandQueue = commandQueue.commandQueue;
|
||||
mContext->pipelineStateCache.setDevice(mContext->device);
|
||||
@@ -1408,9 +1407,6 @@ void MetalDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
void MetalDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
|
||||
return;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(isInRenderPass(mContext))
|
||||
<< "setPushConstant must be called inside a render pass.";
|
||||
assert_invariant(static_cast<size_t>(stage) < mContext->currentPushConstants.size());
|
||||
@@ -1718,9 +1714,6 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
}
|
||||
|
||||
void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
|
||||
return;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "bindPipeline() without a valid command encoder.";
|
||||
DEBUG_LOG("bindPipeline(ps = { program = %d }))\n", ps.program.getId());
|
||||
@@ -1876,9 +1869,6 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
}
|
||||
|
||||
void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
|
||||
return;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
<< "bindRenderPrimitive() without a valid command encoder.";
|
||||
|
||||
|
||||
@@ -36,42 +36,6 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
// A wrapper around a C void* pointer that will free the memory when deallocated, unless the pointer
|
||||
// is first reset.
|
||||
// ReleasablePointer serves a similar purpose as std::unique_ptr, but avoids false positives with
|
||||
// TSAN and Objective-C blocks.
|
||||
@interface ReleasablePointer : NSObject
|
||||
@property(nonatomic, readonly) void* p;
|
||||
@end
|
||||
|
||||
@implementation ReleasablePointer {
|
||||
void* _p;
|
||||
}
|
||||
|
||||
- (id)initWithPointer:(void*)p {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_p = p;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_p) {
|
||||
free(_p);
|
||||
}
|
||||
}
|
||||
|
||||
- (void*)p {
|
||||
return _p;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_p = nullptr;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
@@ -392,17 +356,15 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
};
|
||||
|
||||
// This callback pointer will be captured by the block. Even if the scheduled handler is never
|
||||
// called, the ReleasablePointer will still ensure we don't leak memory.
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
uint64_t const flags = frameScheduled.flags;
|
||||
ReleasablePointer* callback = [[ReleasablePointer alloc]
|
||||
initWithPointer:new Callback(frameScheduled.callback, drawable, layerDrawableMutex,
|
||||
context.driver, flags)];
|
||||
__block auto callback = std::make_unique<Callback>(
|
||||
frameScheduled.callback, drawable, layerDrawableMutex, context.driver, flags);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addScheduledHandler:^(id<MTLCommandBuffer> cb) {
|
||||
Callback* user = static_cast<Callback*>(callback.p);
|
||||
[callback reset];
|
||||
Callback* user = callback.release();
|
||||
if (flags & SwapChain::CALLBACK_DEFAULT_USE_METAL_COMPLETION_HANDLER) {
|
||||
Callback::func(user);
|
||||
} else {
|
||||
@@ -428,14 +390,12 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||
|
||||
// This callback pointer will be captured by the block. Even if the completed handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
ReleasablePointer* callback =
|
||||
[[ReleasablePointer alloc] initWithPointer:new Callback(frameCompleted.callback)];
|
||||
__block auto callback = std::make_unique<Callback>(frameCompleted.callback);
|
||||
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
Callback* user = static_cast<Callback*>(callback.p);
|
||||
[callback reset];
|
||||
Callback* user = callback.release();
|
||||
driver->scheduleCallback(handler, user, &Callback::func);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -45,9 +45,6 @@ PlatformMetal::~PlatformMetal() noexcept {
|
||||
}
|
||||
|
||||
Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure
|
||||
? DrawableFailureBehavior::ABORT_FRAME
|
||||
: DrawableFailureBehavior::PANIC;
|
||||
return MetalDriverFactory::create(this, driverConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -896,17 +896,6 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
t->externalTexture = mPlatform.createExternalImageTexture();
|
||||
if (t->externalTexture) {
|
||||
if (target == SamplerType::SAMPLER_EXTERNAL) {
|
||||
if (UTILS_LIKELY(gl.ext.OES_EGL_image_external_essl3)) {
|
||||
t->externalTexture->target = GL_TEXTURE_EXTERNAL_OES;
|
||||
} else {
|
||||
// revert to texture 2D if external is not supported; what else can we do?
|
||||
t->externalTexture->target = GL_TEXTURE_2D;
|
||||
}
|
||||
} else {
|
||||
t->externalTexture->target = getTextureTargetNotExternal(target);
|
||||
}
|
||||
|
||||
t->gl.target = t->externalTexture->target;
|
||||
t->gl.id = t->externalTexture->id;
|
||||
// internalFormat actually depends on the external image, but it doesn't matter
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t INVALID_VK_INDEX = 0xFFFFFFFF;
|
||||
constexpr uint32_t const INVALID_VK_INDEX = 0xFFFFFFFF;
|
||||
|
||||
using ExtensionSet = VulkanPlatform::ExtensionSet;
|
||||
|
||||
@@ -56,25 +56,25 @@ inline bool setContains(ExtensionSet const& set, utils::CString const& extension
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
// These strings need to be allocated outside a function stack
|
||||
const std::string_view DESIRED_LAYERS[] = {
|
||||
"VK_LAYER_KHRONOS_validation",
|
||||
"VK_LAYER_KHRONOS_validation",
|
||||
#if FVK_ENABLED(FVK_DEBUG_DUMP_API)
|
||||
"VK_LAYER_LUNARG_api_dump",
|
||||
"VK_LAYER_LUNARG_api_dump",
|
||||
#endif
|
||||
#if defined(ENABLE_RENDERDOC)
|
||||
"VK_LAYER_RENDERDOC_Capture",
|
||||
"VK_LAYER_RENDERDOC_Capture",
|
||||
#endif
|
||||
};
|
||||
|
||||
FixedCapacityVector<const char*> getEnabledLayers() {
|
||||
constexpr size_t kMaxEnabledLayersCount = sizeof(DESIRED_LAYERS) / sizeof(DESIRED_LAYERS[0]);
|
||||
|
||||
FixedCapacityVector<VkLayerProperties> const availableLayers
|
||||
const FixedCapacityVector<VkLayerProperties> availableLayers
|
||||
= fvkutils::enumerate(vkEnumerateInstanceLayerProperties);
|
||||
|
||||
auto enabledLayers = FixedCapacityVector<const char*>::with_capacity(kMaxEnabledLayersCount);
|
||||
for (auto const& desired: DESIRED_LAYERS) {
|
||||
for (VkLayerProperties const& layer: availableLayers) {
|
||||
std::string_view const availableLayer(layer.layerName);
|
||||
for (const VkLayerProperties& layer: availableLayers) {
|
||||
const std::string_view availableLayer(layer.layerName);
|
||||
if (availableLayer == desired) {
|
||||
enabledLayers.push_back(desired.data());
|
||||
break;
|
||||
@@ -127,13 +127,13 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
// Since we don't have any vendor-specific workarounds yet, there's no need to make this
|
||||
// mapping in code. The "deviceName" string informally reveals the marketing name for the
|
||||
// GPU. (e.g., Quadro)
|
||||
uint32_t const driverVersion = deviceProperties.driverVersion;
|
||||
uint32_t const vendorID = deviceProperties.vendorID;
|
||||
uint32_t const deviceID = deviceProperties.deviceID;
|
||||
int const major = VK_VERSION_MAJOR(deviceProperties.apiVersion);
|
||||
int const minor = VK_VERSION_MINOR(deviceProperties.apiVersion);
|
||||
const uint32_t driverVersion = deviceProperties.driverVersion;
|
||||
const uint32_t vendorID = deviceProperties.vendorID;
|
||||
const uint32_t deviceID = deviceProperties.deviceID;
|
||||
const int major = VK_VERSION_MAJOR(deviceProperties.apiVersion);
|
||||
const int minor = VK_VERSION_MINOR(deviceProperties.apiVersion);
|
||||
|
||||
FixedCapacityVector<VkPhysicalDevice> const physicalDevices
|
||||
const FixedCapacityVector<VkPhysicalDevice> physicalDevices
|
||||
= fvkutils::enumerate(vkEnumeratePhysicalDevices, instance);
|
||||
|
||||
FVK_LOGI << "Selected physical device '" << deviceProperties.deviceName << "' from "
|
||||
@@ -148,8 +148,8 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
void printDepthFormats(VkPhysicalDevice device) {
|
||||
// For diagnostic purposes, print useful information about available depth formats.
|
||||
// Note that Vulkan is more constrained than OpenGL ES 3.1 in this area.
|
||||
constexpr VkFormatFeatureFlags required =
|
||||
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||
FVK_LOGI << "Sampleable depth formats: ";
|
||||
for (VkFormat format : fvkutils::ALL_VK_FORMATS) {
|
||||
VkFormatProperties props;
|
||||
@@ -168,13 +168,11 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
|
||||
VK_KHR_SURFACE_EXTENSION_NAME,
|
||||
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
|
||||
|
||||
// Request these if available.
|
||||
// Request these if available.
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
|
||||
#endif
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
|
||||
@@ -184,8 +182,8 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
|
||||
FixedCapacityVector<VkExtensionProperties> const availableExts =
|
||||
fvkutils::enumerate(vkEnumerateInstanceExtensionProperties,
|
||||
static_cast<char const*>(nullptr) /* pLayerName */);
|
||||
for (auto const& extension: availableExts) {
|
||||
utils::CString name { extension.extensionName };
|
||||
for (auto const& extProps: availableExts) {
|
||||
utils::CString name { extProps.extensionName };
|
||||
|
||||
// To workaround an Adreno bug where the extension name could be of 0 length.
|
||||
if (name.size() == 0) {
|
||||
@@ -202,24 +200,22 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
|
||||
ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
ExtensionSet const TARGET_EXTS = {
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
|
||||
VK_EXT_DEBUG_MARKER_EXTENSION_NAME,
|
||||
#endif
|
||||
// We only support external image for Android for now, but nothing bars us from
|
||||
// supporting other platforms.
|
||||
// We only support external image for Android for now, but nothing bars us from
|
||||
// supporting other platforms.
|
||||
#if defined(__ANDROID__)
|
||||
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
|
||||
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
||||
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||
#endif
|
||||
// MoltenVk is the only non-conformant implementation we're interested in.
|
||||
#if defined(__APPLE__)
|
||||
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
|
||||
#endif
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
|
||||
VK_KHR_MULTIVIEW_EXTENSION_NAME,
|
||||
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
|
||||
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
||||
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||
#endif
|
||||
|
||||
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
|
||||
VK_KHR_MULTIVIEW_EXTENSION_NAME,
|
||||
};
|
||||
ExtensionSet exts;
|
||||
// Identify supported physical device extensions
|
||||
@@ -242,18 +238,9 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
}
|
||||
|
||||
VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
// Create the Vulkan instance.
|
||||
VkApplicationInfo appInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
.pEngineName = "Filament",
|
||||
.apiVersion =
|
||||
VK_MAKE_API_VERSION(0, FVK_REQUIRED_VERSION_MAJOR, FVK_REQUIRED_VERSION_MINOR, 0),
|
||||
};
|
||||
|
||||
VkInstance instance;
|
||||
VkInstanceCreateInfo instanceCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
.pApplicationInfo = &appInfo,
|
||||
};
|
||||
bool validationFeaturesSupported = false;
|
||||
|
||||
@@ -284,10 +271,10 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
|
||||
|
||||
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 6
|
||||
// instance extensions here in the common code. So that's a max of 8.
|
||||
constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 8;
|
||||
char const* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
|
||||
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 5
|
||||
// instance extensions here in the common code. So that's a max of 7.
|
||||
static constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 8;
|
||||
const char* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
|
||||
uint32_t enabledExtensionCount = 0;
|
||||
|
||||
if (validationFeaturesSupported) {
|
||||
@@ -299,23 +286,29 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
ppEnabledExtensions[enabledExtensionCount++] = requiredExt.data();
|
||||
}
|
||||
|
||||
// Create the Vulkan instance.
|
||||
VkApplicationInfo appInfo = {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pEngineName = "Filament";
|
||||
appInfo.apiVersion
|
||||
= VK_MAKE_API_VERSION(0, FVK_REQUIRED_VERSION_MAJOR, FVK_REQUIRED_VERSION_MINOR, 0);
|
||||
instanceCreateInfo.pApplicationInfo = &appInfo;
|
||||
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
|
||||
instanceCreateInfo.ppEnabledExtensionNames = ppEnabledExtensions;
|
||||
if (setContains(requiredExts, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
|
||||
instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
}
|
||||
|
||||
// Validation features
|
||||
VkValidationFeaturesEXT features = {
|
||||
.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
|
||||
};
|
||||
VkValidationFeatureEnableEXT enables[] = {
|
||||
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
|
||||
VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,
|
||||
};
|
||||
VkValidationFeaturesEXT features = {
|
||||
.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
|
||||
.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]),
|
||||
.pEnabledValidationFeatures = enables,
|
||||
};
|
||||
if (validationFeaturesSupported) {
|
||||
features.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]);
|
||||
features.pEnabledValidationFeatures = enables;
|
||||
chainStruct(&instanceCreateInfo, &features);
|
||||
}
|
||||
|
||||
@@ -343,15 +336,16 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
requestExtensions.push_back(ext.data());
|
||||
}
|
||||
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
|
||||
deviceQueueCreateInfo[0] = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = graphicsQueueFamilyIndex,
|
||||
.queueCount = 1,
|
||||
.pQueuePriorities = &queuePriority[0],
|
||||
};
|
||||
deviceQueueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
deviceQueueCreateInfo[0].queueFamilyIndex = graphicsQueueFamilyIndex;
|
||||
deviceQueueCreateInfo[0].queueCount = 1;
|
||||
deviceQueueCreateInfo[0].pQueuePriorities = &queuePriority[0];
|
||||
// Protected queue
|
||||
deviceQueueCreateInfo[1] = deviceQueueCreateInfo[0];
|
||||
deviceQueueCreateInfo[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
deviceQueueCreateInfo[1].flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
|
||||
deviceQueueCreateInfo[1].queueFamilyIndex = protectedGraphicsQueueFamilyIndex;
|
||||
deviceQueueCreateInfo[1].queueCount = 1;
|
||||
deviceQueueCreateInfo[1].pQueuePriorities = &queuePriority[0];
|
||||
|
||||
bool const hasProtectedQueue = protectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX;
|
||||
deviceCreateInfo.queueCreateInfoCount = hasProtectedQueue ? 2 : 1;
|
||||
@@ -360,21 +354,21 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
.depthClamp = features.features.depthClamp,
|
||||
.samplerAnisotropy = features.features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.features.textureCompressionBC,
|
||||
.shaderClipDistance = features.features.shaderClipDistance,
|
||||
.depthClamp = features.features.depthClamp,
|
||||
.samplerAnisotropy = features.features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.features.textureCompressionBC,
|
||||
.shaderClipDistance = features.features.shaderClipDistance,
|
||||
};
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
|
||||
|
||||
VkPhysicalDevicePortabilitySubsetFeaturesKHR portability = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
|
||||
.imageViewFormatSwizzle = VK_TRUE,
|
||||
.imageView2DOn3DImage = requestImageView2DOn3DImage ? VK_TRUE : VK_FALSE,
|
||||
.imageView2DOn3DImage = requestImageView2DOn3DImage ? VK_TRUE : VK_FALSE,
|
||||
.mutableComparisonSamplers = VK_TRUE,
|
||||
};
|
||||
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
|
||||
@@ -410,8 +404,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
// This method is used to enable/disable extensions based on external factors (i.e.
|
||||
// driver/device workarounds).
|
||||
std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
|
||||
Platform::DriverConfig const& driverConfig, ExtensionSet const& instExts,
|
||||
ExtensionSet const& deviceExts) noexcept {
|
||||
ExtensionSet const& instExts, ExtensionSet const& deviceExts) {
|
||||
ExtensionSet newInstExts = instExts;
|
||||
ExtensionSet newDeviceExts = deviceExts;
|
||||
|
||||
@@ -431,10 +424,6 @@ std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (driverConfig.stereoscopicType != StereoscopicType::MULTIVIEW) {
|
||||
newDeviceExts.erase(VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
return std::tuple(newInstExts, newDeviceExts);
|
||||
}
|
||||
|
||||
@@ -468,17 +457,22 @@ uint32_t identifyGraphicsQueueFamilyIndex(VkPhysicalDevice physicalDevice, VkQue
|
||||
// Enum based on:
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPhysicalDeviceType.html
|
||||
inline int deviceTypeOrder(VkPhysicalDeviceType deviceType) {
|
||||
constexpr std::array<VkPhysicalDeviceType, 5> TYPES = {
|
||||
VK_PHYSICAL_DEVICE_TYPE_OTHER,
|
||||
VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU,
|
||||
VK_PHYSICAL_DEVICE_TYPE_CPU,
|
||||
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU,
|
||||
VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU,
|
||||
};
|
||||
if (auto itr = std::find(TYPES.begin(), TYPES.end(), deviceType); itr != TYPES.end()) {
|
||||
return std::distance(TYPES.begin(), itr);
|
||||
switch (deviceType) {
|
||||
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
||||
return 5;
|
||||
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
||||
return 4;
|
||||
case VK_PHYSICAL_DEVICE_TYPE_CPU:
|
||||
return 3;
|
||||
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
|
||||
return 2;
|
||||
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
||||
return 1;
|
||||
default:
|
||||
FVK_LOGW << "deviceTypeOrder: Unexpected deviceType: " << deviceType
|
||||
<< utils::io::endl;
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
@@ -521,19 +515,20 @@ VkPhysicalDevice selectPhysicalDevice(VkInstance instance,
|
||||
FixedCapacityVector<VkExtensionProperties> const extensions
|
||||
= fvkutils::enumerate(vkEnumerateDeviceExtensionProperties,
|
||||
candidateDevice, static_cast<char const*>(nullptr) /* pLayerName */);
|
||||
bool const supportsSwapchain =
|
||||
std::any_of(extensions.begin(), extensions.end(), [](auto const& ext) {
|
||||
return !strcmp(ext.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
});
|
||||
bool supportsSwapchain = false;
|
||||
for (auto const& extension: extensions) {
|
||||
if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
|
||||
supportsSwapchain = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!supportsSwapchain) {
|
||||
continue;
|
||||
}
|
||||
deviceList[deviceInd] = {
|
||||
.device = candidateDevice,
|
||||
.deviceType = targetDeviceProperties.deviceType,
|
||||
.index = (int8_t) deviceInd,
|
||||
.name = targetDeviceProperties.deviceName,
|
||||
};
|
||||
deviceList[deviceInd].device = candidateDevice;
|
||||
deviceList[deviceInd].deviceType = targetDeviceProperties.deviceType;
|
||||
deviceList[deviceInd].index = deviceInd;
|
||||
deviceList[deviceInd].name = targetDeviceProperties.deviceName;
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(gpuPreference.index < static_cast<int32_t>(deviceList.size()))
|
||||
@@ -574,7 +569,7 @@ fvkutils::VkFormatList findAttachmentDepthStencilFormats(VkPhysicalDevice device
|
||||
VkFormatFeatureFlags const features = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||||
|
||||
// The ordering here indicates the preference of choosing depth+stencil format.
|
||||
constexpr VkFormat formats[] = {
|
||||
VkFormat const formats[] = {
|
||||
VK_FORMAT_D32_SFLOAT,
|
||||
VK_FORMAT_X8_D24_UNORM_PACK32,
|
||||
|
||||
@@ -596,7 +591,7 @@ fvkutils::VkFormatList findAttachmentDepthStencilFormats(VkPhysicalDevice device
|
||||
|
||||
fvkutils::VkFormatList findBlittableDepthStencilFormats(VkPhysicalDevice device) {
|
||||
std::vector<VkFormat> selectedFormats;
|
||||
constexpr VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
|
||||
VkFormatFeatureFlags const required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
|
||||
VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
|
||||
for (VkFormat format : fvkutils::ALL_VK_FORMATS) {
|
||||
if (fvkutils::isVkDepthFormat(format)) {
|
||||
@@ -656,7 +651,7 @@ void VulkanPlatform::terminate() {
|
||||
|
||||
// This is the main entry point for context creation.
|
||||
Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
// Load Vulkan entry points.
|
||||
FILAMENT_CHECK_POSTCONDITION(bluevk::initialize()) << "BlueVK is unable to load entry points.";
|
||||
|
||||
@@ -684,6 +679,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
}
|
||||
|
||||
VulkanContext context;
|
||||
|
||||
ExtensionSet instExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
@@ -708,9 +704,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
instExts.merge(getRequiredInstanceExtensions());
|
||||
}
|
||||
if (mImpl->mInstance == VK_NULL_HANDLE) {
|
||||
mImpl->mInstance = createInstance(instExts);
|
||||
}
|
||||
|
||||
mImpl->mInstance
|
||||
= mImpl->mInstance == VK_NULL_HANDLE ? createInstance(instExts) : mImpl->mInstance;
|
||||
assert_invariant(mImpl->mInstance != VK_NULL_HANDLE);
|
||||
|
||||
bluevk::bindInstance(mImpl->mInstance);
|
||||
@@ -720,9 +716,9 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
FILAMENT_CHECK_PRECONDITION(!(hasGPUPreference && sharedContext))
|
||||
<< "Cannot both share context and indicate GPU preference";
|
||||
|
||||
if (mImpl->mPhysicalDevice == VK_NULL_HANDLE) {
|
||||
mImpl->mPhysicalDevice = selectPhysicalDevice(mImpl->mInstance, pref);
|
||||
}
|
||||
mImpl->mPhysicalDevice = mImpl->mPhysicalDevice == VK_NULL_HANDLE
|
||||
? selectPhysicalDevice(mImpl->mInstance, pref)
|
||||
: mImpl->mPhysicalDevice;
|
||||
assert_invariant(mImpl->mPhysicalDevice != VK_NULL_HANDLE);
|
||||
|
||||
printDeviceInfo(mImpl->mInstance, mImpl->mPhysicalDevice);
|
||||
@@ -736,52 +732,59 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
|
||||
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
|
||||
|
||||
// We know we need to allocate the protected version of the VK objects
|
||||
context.mProtectedMemorySupported =
|
||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
|
||||
vkGetPhysicalDeviceMemoryProperties(mImpl->mPhysicalDevice, &context.mMemoryProperties);
|
||||
|
||||
if (mImpl->mGraphicsQueueFamilyIndex == INVALID_VK_INDEX) {
|
||||
mImpl->mGraphicsQueueFamilyIndex =
|
||||
identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice, VK_QUEUE_GRAPHICS_BIT);
|
||||
mImpl->mGraphicsQueueFamilyIndex
|
||||
= mImpl->mGraphicsQueueFamilyIndex == INVALID_VK_INDEX
|
||||
? identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice,
|
||||
VK_QUEUE_GRAPHICS_BIT)
|
||||
: mImpl->mGraphicsQueueFamilyIndex;
|
||||
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
|
||||
// We know we need to allocate the protected version of the VK objects
|
||||
context.mProtectedMemorySupported =
|
||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||
if (context.mProtectedMemorySupported) {
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex
|
||||
= identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice,
|
||||
(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
||||
assert_invariant(mImpl->mProtectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
}
|
||||
|
||||
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
|
||||
if (context.mPhysicalDeviceFeatures.features.shaderClipDistance == VK_TRUE
|
||||
&& driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
|
||||
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
|
||||
// At this point, we should have a family index that points to a family that has > 0 queues for
|
||||
// graphics. In which case, we will allocate one queue for all of Filament (and assumes at least
|
||||
// one has been allocated by the client if context was shared). If the index of the target queue
|
||||
// within the family hasn't been provided by the client, we assume it to be 0.
|
||||
if (mImpl->mGraphicsQueueIndex == INVALID_VK_INDEX) {
|
||||
mImpl->mGraphicsQueueIndex = 0;
|
||||
}
|
||||
mImpl->mGraphicsQueueIndex
|
||||
= mImpl->mGraphicsQueueIndex == INVALID_VK_INDEX ? 0 : mImpl->mGraphicsQueueIndex;
|
||||
|
||||
if (context.mProtectedMemorySupported) {
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
|
||||
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
||||
// Applying the same logic to the protected queue index (Not sure about shared context and
|
||||
// protection)
|
||||
if (mImpl->mProtectedGraphicsQueueIndex == INVALID_VK_INDEX) {
|
||||
mImpl->mProtectedGraphicsQueueIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
|
||||
if (driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
|
||||
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
|
||||
}
|
||||
// Applying the same logic to the protected queue index (Not sure about shared context and protection)
|
||||
mImpl->mProtectedGraphicsQueueIndex
|
||||
= mImpl->mProtectedGraphicsQueueIndex == INVALID_VK_INDEX ? 0 :
|
||||
mImpl->mProtectedGraphicsQueueIndex;
|
||||
|
||||
ExtensionSet deviceExts;
|
||||
// If using a shared context, we do not assume any extensions.
|
||||
if (!mImpl->mSharedContext) {
|
||||
deviceExts = getDeviceExtensions(mImpl->mPhysicalDevice);
|
||||
auto [prunedInstExts, prunedDeviceExts] =
|
||||
pruneExtensions(mImpl->mPhysicalDevice, driverConfig, instExts, deviceExts);
|
||||
auto [prunedInstExts, prunedDeviceExts]
|
||||
= pruneExtensions(mImpl->mPhysicalDevice, instExts, deviceExts);
|
||||
instExts = prunedInstExts;
|
||||
deviceExts = prunedDeviceExts;
|
||||
|
||||
if (driverConfig.stereoscopicType != StereoscopicType::MULTIVIEW) {
|
||||
deviceExts.erase(VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
bool requestPortabilitySubsetImageView2DOn3DImage = false;
|
||||
@@ -794,32 +797,26 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
context.mPortabilitySubsetFeatures.imageView2DOn3DImage == VK_TRUE;
|
||||
}
|
||||
|
||||
if (mImpl->mDevice == VK_NULL_HANDLE) {
|
||||
mImpl->mDevice =
|
||||
createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures,
|
||||
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
|
||||
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
|
||||
}
|
||||
|
||||
mImpl->mDevice =
|
||||
mImpl->mDevice == VK_NULL_HANDLE
|
||||
? createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures,
|
||||
mImpl->mGraphicsQueueFamilyIndex,
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex, deviceExts,
|
||||
requestPortabilitySubsetImageView2DOn3DImage)
|
||||
: mImpl->mDevice;
|
||||
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
||||
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
assert_invariant(mImpl->mGraphicsQueueIndex != INVALID_VK_INDEX);
|
||||
|
||||
vkGetDeviceQueue(mImpl->mDevice, mImpl->mGraphicsQueueFamilyIndex, mImpl->mGraphicsQueueIndex,
|
||||
&mImpl->mGraphicsQueue);
|
||||
assert_invariant(mImpl->mGraphicsQueue != VK_NULL_HANDLE);
|
||||
|
||||
if (context.mProtectedMemorySupported) {
|
||||
assert_invariant(mImpl->mProtectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
|
||||
assert_invariant(mImpl->mProtectedGraphicsQueueIndex != INVALID_VK_INDEX);
|
||||
VkDeviceQueueInfo2 info = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
|
||||
.flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT,
|
||||
.queueFamilyIndex = mImpl->mProtectedGraphicsQueueFamilyIndex,
|
||||
.queueIndex = mImpl->mProtectedGraphicsQueueIndex,
|
||||
};
|
||||
VkDeviceQueueInfo2 info = {};
|
||||
info.queueFamilyIndex = mImpl->mProtectedGraphicsQueueFamilyIndex;
|
||||
info.queueIndex = mImpl->mProtectedGraphicsQueueIndex;
|
||||
info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2;
|
||||
info.flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
|
||||
vkGetDeviceQueue2(mImpl->mDevice, &info, &mImpl->mProtectedGraphicsQueue);
|
||||
assert_invariant(mImpl->mProtectedGraphicsQueue != VK_NULL_HANDLE);
|
||||
}
|
||||
|
||||
// Store the extension support in the context
|
||||
@@ -835,14 +832,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
}
|
||||
|
||||
// Check the availability of lazily allocated memory
|
||||
context.mLazilyAllocatedMemorySupported = false;
|
||||
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
|
||||
++i) {
|
||||
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
|
||||
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
|
||||
context.mLazilyAllocatedMemorySupported = true;
|
||||
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
break;
|
||||
{
|
||||
context.mLazilyAllocatedMemorySupported = false;
|
||||
const uint32_t typeCount = context.mMemoryProperties.memoryTypeCount;
|
||||
for(uint32_t i = 0; i < typeCount; ++i) {
|
||||
const VkMemoryType type = context.mMemoryProperties.memoryTypes[i];
|
||||
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
|
||||
context.mLazilyAllocatedMemorySupported = true;
|
||||
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,15 +317,6 @@ public:
|
||||
*/
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired.
|
||||
*
|
||||
* Each frame rendered requires a CAMetalDrawable texture, which is
|
||||
* presented on-screen at the completion of each frame. These are
|
||||
* limited and provided round-robin style by the system.
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
|
||||
@@ -91,9 +91,6 @@ public:
|
||||
/** @return Whether a backend supports a particular format. */
|
||||
static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept;
|
||||
|
||||
/** @return Whether a backend supports mipmapping of a particular format. */
|
||||
static bool isTextureFormatMipmappable(Engine& engine, InternalFormat format) noexcept;
|
||||
|
||||
/** @return Whether this backend supports protected textures. */
|
||||
static bool isProtectedTexturesSupported(Engine& engine) noexcept;
|
||||
|
||||
@@ -103,8 +100,6 @@ public:
|
||||
static size_t computeTextureDataSize(Format format, Type type,
|
||||
size_t stride, size_t height, size_t alignment) noexcept;
|
||||
|
||||
/** @return Whether a combination of texture format, pixel format and type is valid. */
|
||||
static bool validatePixelFormatAndType(InternalFormat internalFormat, Format format, Type type) noexcept;
|
||||
|
||||
/**
|
||||
* Options for environment prefiltering into reflection map
|
||||
|
||||
@@ -116,12 +116,6 @@ static bool fuzzyEqual(mat4f const& UTILS_RESTRICT l, mat4f const& UTILS_RESTRIC
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
size_t Froxelizer::getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept {
|
||||
// Make sure that targetSize is 16-byte aligned so that it'll fit properly into an array of
|
||||
// uvec4.
|
||||
size_t const targetSize = (driverApi.getMaxUniformBufferSize() / 16) * 16;
|
||||
return std::min(FROXEL_BUFFER_MAX_ENTRY_COUNT * sizeof(FroxelEntry), targetSize);
|
||||
}
|
||||
|
||||
Froxelizer::Froxelizer(FEngine& engine)
|
||||
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
|
||||
@@ -137,14 +131,14 @@ Froxelizer::Froxelizer(FEngine& engine)
|
||||
return;
|
||||
}
|
||||
|
||||
size_t const froxelBufferByteCount = getFroxelBufferByteCount(engine.getDriverApi());
|
||||
mFroxelBufferEntryCount = froxelBufferByteCount / sizeof(FroxelEntry);
|
||||
mFroxelBufferEntryCount = std::min(
|
||||
FROXEL_BUFFER_MAX_ENTRY_COUNT,
|
||||
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
|
||||
|
||||
mRecordsBuffer = driverApi.createBufferObject(RECORD_BUFFER_ENTRY_COUNT,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
|
||||
mFroxelsBuffer = driverApi.createBufferObject(
|
||||
froxelBufferByteCount,
|
||||
mFroxelsBuffer = driverApi.createBufferObject(getFroxelBufferEntryCount() * 16u,
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
}
|
||||
|
||||
@@ -533,7 +527,7 @@ std::pair<size_t, size_t> Froxelizer::clipToIndices(float2 const& clip) const no
|
||||
void Froxelizer::commit(DriverApi& driverApi) {
|
||||
// send data to GPU
|
||||
driverApi.updateBufferObject(mFroxelsBuffer,
|
||||
{ mFroxelBufferUser.data(), getFroxelBufferEntryCount() * sizeof(FroxelEntry) }, 0);
|
||||
{ mFroxelBufferUser.data(), getFroxelBufferEntryCount() * 16u }, 0);
|
||||
|
||||
driverApi.updateBufferObject(mRecordsBuffer,
|
||||
{ mRecordBufferUser.data(), RECORD_BUFFER_ENTRY_COUNT }, 0);
|
||||
|
||||
@@ -35,16 +35,15 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
// The number of froxel buffer entries is determined by max UBO size (see
|
||||
// getFroxelBufferByteCount()). We also introduce the limit below because increasing the number of
|
||||
// froxels adds more pressure on the "record buffer" which stores the light indices per froxel. The
|
||||
// record buffer is limited to min(16K[ubo], 64K[uint16]) entries. In practice some froxels are not
|
||||
// used, so we can store more.
|
||||
// Max number of froxels limited by:
|
||||
// - max ubo size [min 16KiB]
|
||||
//
|
||||
// Also, increasing the number of froxels adds more pressure on the "record buffer" which stores
|
||||
// the light indices per froxel. The record buffer is limited to min(16K[ubo], 64K[uint16]) entries,
|
||||
// so with 8192 froxels, we can store 2 lights per froxels assuming they're all used. In practice,
|
||||
// some froxels are not used, so we can store more.
|
||||
constexpr size_t FROXEL_BUFFER_MAX_ENTRY_COUNT = 8192;
|
||||
|
||||
// Froxel buffer UBO is an array of uvec4. Make sure that the buffer is properly aligned.
|
||||
static_assert(FROXEL_BUFFER_MAX_ENTRY_COUNT % 4 == 0u);
|
||||
|
||||
class FEngine;
|
||||
class FCamera;
|
||||
class FTexture;
|
||||
@@ -147,7 +146,6 @@ public:
|
||||
inline uint16_t offset() const noexcept { return u32 >> 16u; }
|
||||
uint32_t u32 = 0;
|
||||
};
|
||||
static_assert(sizeof(FroxelEntry) == 4u);
|
||||
|
||||
// we can't change this easily because the shader expects 16 indices per uint4
|
||||
using RecordBufferType = uint8_t;
|
||||
@@ -159,8 +157,6 @@ public:
|
||||
// with 256 lights this implies 8 jobs (256 / 32) for froxelization.
|
||||
using LightGroupType = uint32_t;
|
||||
|
||||
static size_t getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept;
|
||||
|
||||
private:
|
||||
size_t getFroxelBufferEntryCount() const noexcept {
|
||||
return mFroxelBufferEntryCount;
|
||||
|
||||
@@ -82,10 +82,6 @@ bool Texture::isTextureFormatSupported(Engine& engine, InternalFormat const form
|
||||
return FTexture::isTextureFormatSupported(downcast(engine), format);
|
||||
}
|
||||
|
||||
bool Texture::isTextureFormatMipmappable(Engine& engine, InternalFormat const format) noexcept {
|
||||
return FTexture::isTextureFormatMipmappable(downcast(engine), format);
|
||||
}
|
||||
|
||||
bool Texture::isProtectedTexturesSupported(Engine& engine) noexcept {
|
||||
return FTexture::isProtectedTexturesSupported(downcast(engine));
|
||||
}
|
||||
@@ -104,8 +100,4 @@ void Texture::generatePrefilterMipmap(Engine& engine, PixelBufferDescriptor&& bu
|
||||
downcast(this)->generatePrefilterMipmap(downcast(engine), std::move(buffer), faceOffsets, options);
|
||||
}
|
||||
|
||||
bool Texture::validatePixelFormatAndType(InternalFormat internalFormat, Format format, Type type) noexcept {
|
||||
return FTexture::validatePixelFormatAndType(internalFormat, format, type);
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -137,7 +137,6 @@ Engine* FEngine::create(Builder const& builder) {
|
||||
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -731,7 +730,6 @@ int FEngine::loop() {
|
||||
.forceGLES2Context = mConfig.forceGLES2Context,
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
|
||||
@@ -980,9 +980,9 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Builder const& b
|
||||
int const maxInstanceCount = (engine.getActiveFeatureLevel() == FeatureLevel::FEATURE_LEVEL_0)
|
||||
? 1 : CONFIG_MAX_INSTANCES;
|
||||
|
||||
// The 16u below denotes the 16 bytes in a uvec4, which is how the froxel buffer is stored.
|
||||
int const maxFroxelBufferHeight =
|
||||
int(Froxelizer::getFroxelBufferByteCount(engine.getDriverApi()) / 16u);
|
||||
int const maxFroxelBufferHeight = int(std::min(
|
||||
FROXEL_BUFFER_MAX_ENTRY_COUNT / 4,
|
||||
engine.getDriverApi().getMaxUniformBufferSize() / 16u));
|
||||
|
||||
bool const staticTextureWorkaround =
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR);
|
||||
|
||||
@@ -61,7 +61,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
: mMaterial(material),
|
||||
mDescriptorSet(material->getDescriptorSetLayout()),
|
||||
mCulling(CullingMode::BACK),
|
||||
mShadowCulling(CullingMode::BACK),
|
||||
mDepthFunc(RasterState::DepthFunc::LE),
|
||||
mColorWrite(false),
|
||||
mDepthWrite(false),
|
||||
@@ -93,7 +92,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
// We inherit the resolved culling mode rather than the builder-set culling mode.
|
||||
// This preserves the property whereby double-sidedness automatically disables culling.
|
||||
mCulling = rasterState.culling;
|
||||
mShadowCulling = rasterState.culling;
|
||||
mColorWrite = rasterState.colorWrite;
|
||||
mDepthWrite = rasterState.depthWrite;
|
||||
mDepthFunc = rasterState.depthFunc;
|
||||
@@ -128,7 +126,6 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
|
||||
mSpecularAntiAliasingVariance(other->mSpecularAntiAliasingVariance),
|
||||
mSpecularAntiAliasingThreshold(other->mSpecularAntiAliasingThreshold),
|
||||
mCulling(other->mCulling),
|
||||
mShadowCulling(other->mShadowCulling),
|
||||
mDepthFunc(other->mDepthFunc),
|
||||
mColorWrite(other->mColorWrite),
|
||||
mDepthWrite(other->mDepthWrite),
|
||||
|
||||
@@ -137,10 +137,7 @@ public:
|
||||
|
||||
void setTransparencyMode(TransparencyMode mode) noexcept;
|
||||
|
||||
void setCullingMode(CullingMode const culling) noexcept {
|
||||
mCulling = culling;
|
||||
mShadowCulling = culling;
|
||||
}
|
||||
void setCullingMode(CullingMode const culling) noexcept { mCulling = culling; }
|
||||
|
||||
void setCullingMode(CullingMode const color, CullingMode const shadow) noexcept {
|
||||
mCulling = color;
|
||||
|
||||
@@ -658,10 +658,6 @@ bool FTexture::isTextureFormatSupported(FEngine& engine, InternalFormat const fo
|
||||
return engine.getDriverApi().isTextureFormatSupported(format);
|
||||
}
|
||||
|
||||
bool FTexture::isTextureFormatMipmappable(FEngine& engine, InternalFormat const format) noexcept {
|
||||
return engine.getDriverApi().isTextureFormatMipmappable(format);
|
||||
}
|
||||
|
||||
bool FTexture::isProtectedTexturesSupported(FEngine& engine) noexcept {
|
||||
return engine.getDriverApi().isProtectedTexturesSupported();
|
||||
}
|
||||
|
||||
@@ -89,9 +89,6 @@ public:
|
||||
// Synchronous call to the backend. Returns whether a backend supports a particular format.
|
||||
static bool isTextureFormatSupported(FEngine& engine, InternalFormat format) noexcept;
|
||||
|
||||
// Synchronous call to the backend. Returns whether a backend supports mipmapping of a particular format.
|
||||
static bool isTextureFormatMipmappable(FEngine& engine, InternalFormat format) noexcept;
|
||||
|
||||
// Synchronous call to the backend. Returns whether a backend supports protected textures.
|
||||
static bool isProtectedTexturesSupported(FEngine& engine) noexcept;
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ FView::FView(FEngine& engine)
|
||||
mDefaultColorGrading = mColorGrading = engine.getDefaultColorGrading();
|
||||
|
||||
mColorPassDescriptorSet.init(
|
||||
engine,
|
||||
mLightUbh,
|
||||
mFroxelizer.getRecordBuffer(),
|
||||
mFroxelizer.getFroxelBuffer());
|
||||
|
||||
@@ -126,17 +126,16 @@ ColorPassDescriptorSet::ColorPassDescriptorSet(FEngine& engine,
|
||||
}
|
||||
|
||||
void ColorPassDescriptorSet::init(
|
||||
FEngine& engine,
|
||||
BufferObjectHandle lights,
|
||||
BufferObjectHandle recordBuffer,
|
||||
BufferObjectHandle froxelBuffer) noexcept {
|
||||
for (auto&& descriptorSet: mDescriptorSet) {
|
||||
descriptorSet.setBuffer(+PerViewBindingPoints::LIGHTS,
|
||||
lights, 0, CONFIG_MAX_LIGHT_COUNT * sizeof(LightsUib));
|
||||
lights, 0, sizeof(LightsUib));
|
||||
descriptorSet.setBuffer(+PerViewBindingPoints::RECORD_BUFFER,
|
||||
recordBuffer, 0, sizeof(FroxelRecordUib));
|
||||
descriptorSet.setBuffer(+PerViewBindingPoints::FROXEL_BUFFER,
|
||||
froxelBuffer, 0, Froxelizer::getFroxelBufferByteCount(engine.getDriverApi()));
|
||||
froxelBuffer, 0, sizeof(FroxelsUib));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,3 +573,4 @@ void ColorPassDescriptorSet::setBuffer(descriptor_binding_t const binding,
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ public:
|
||||
TypedUniformBuffer<PerViewUib>& uniforms) noexcept;
|
||||
|
||||
void init(
|
||||
FEngine& engine,
|
||||
backend::BufferObjectHandle lights,
|
||||
backend::BufferObjectHandle recordBuffer,
|
||||
backend::BufferObjectHandle froxelBuffer) noexcept;
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -489,7 +488,7 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
|
||||
const auto activePassNodesEnd = mActivePassNodesEnd;
|
||||
while (first != activePassNodesEnd) {
|
||||
PassNode *const pass = *first;
|
||||
++first;
|
||||
first++;
|
||||
|
||||
assert_invariant(!pass->isCulled());
|
||||
std::vector<fgviewer::ResourceId> reads;
|
||||
@@ -497,12 +496,7 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
|
||||
for (auto const &edge: readEdges) {
|
||||
// all incoming edges should be valid by construction
|
||||
assert_invariant(mGraph.isEdgeValid(edge));
|
||||
auto resourceNode = static_cast<const ResourceNode*>(mGraph.getNode(edge->from));
|
||||
assert_invariant(resourceNode);
|
||||
if (resourceNode->getRefCount() == 0)
|
||||
continue;
|
||||
|
||||
reads.push_back(resourceNode->resourceHandle.index);
|
||||
reads.push_back(edge->from);
|
||||
}
|
||||
|
||||
std::vector<fgviewer::ResourceId> writes;
|
||||
@@ -513,35 +507,19 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
|
||||
if (!mGraph.isEdgeValid(edge)) {
|
||||
continue;
|
||||
}
|
||||
auto resourceNode = static_cast<const ResourceNode*>(mGraph.getNode(edge->to));
|
||||
assert_invariant(resourceNode);
|
||||
if (resourceNode->getRefCount() == 0)
|
||||
continue;
|
||||
writes.push_back(resourceNode->resourceHandle.index);
|
||||
writes.push_back(edge->to);
|
||||
}
|
||||
passes.emplace_back(utils::CString(pass->getName()),
|
||||
std::move(reads), std::move(writes));
|
||||
}
|
||||
|
||||
std::unordered_map<fgviewer::ResourceId, fgviewer::FrameGraphInfo::Resource> resources;
|
||||
for (const auto &resourceNode: mResourceNodes) {
|
||||
const FrameGraphHandle resourceHandle = resourceNode->resourceHandle;
|
||||
if (resources.find(resourceHandle.index) != resources.end())
|
||||
continue;
|
||||
|
||||
for (const auto &resource: mResourceNodes) {
|
||||
std::vector<fgviewer::FrameGraphInfo::Resource::Property> resourceProps;
|
||||
if (resourceNode->getRefCount() == 0)
|
||||
continue;
|
||||
if (resourceNode->getParentNode() != nullptr) {
|
||||
resourceProps.emplace_back(fgviewer::FrameGraphInfo::Resource::Property {
|
||||
.name = "is_subresource",
|
||||
.value = utils::CString(std::to_string(
|
||||
resourceNode->getParentHandle().index).data())
|
||||
});
|
||||
}
|
||||
resources.emplace(resourceHandle.index, fgviewer::FrameGraphInfo::Resource(
|
||||
resourceHandle.index,
|
||||
utils::CString(resourceNode->getName()),
|
||||
// TODO: Fill in resource properties
|
||||
fgviewer::ResourceId id = resource->getId();
|
||||
resources.emplace(id, fgviewer::FrameGraphInfo::Resource(
|
||||
id, utils::CString(resource->getName()),
|
||||
std::move(resourceProps))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.57.2"
|
||||
spec.version = "1.57.0"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.57.2/filament-v1.57.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.57.0/filament-v1.57.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -23,7 +23,6 @@ set(SRCS
|
||||
src/ApiHandler.h
|
||||
src/DebugServer.cpp
|
||||
src/FrameGraphInfo.cpp
|
||||
src/JsonWriter.cpp
|
||||
)
|
||||
|
||||
# ==================================================================================================
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
* Copyright (C) 2024 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.
|
||||
@@ -36,25 +36,17 @@ public:
|
||||
|
||||
FrameGraphInfo(FrameGraphInfo const &) = delete;
|
||||
|
||||
bool operator==(const FrameGraphInfo& rhs) const;
|
||||
|
||||
struct Pass {
|
||||
Pass(utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes);
|
||||
|
||||
bool operator==(const Pass& rhs) const;
|
||||
|
||||
utils::CString name;
|
||||
std::vector<ResourceId> reads;
|
||||
std::vector<ResourceId> writes;
|
||||
};
|
||||
|
||||
struct Resource {
|
||||
bool operator==(const Resource& rhs) const;
|
||||
|
||||
struct Property {
|
||||
bool operator==(const Property& rhs) const;
|
||||
|
||||
utils::CString name;
|
||||
utils::CString value;
|
||||
};
|
||||
@@ -78,12 +70,6 @@ public:
|
||||
// The incoming passes should be sorted by the execution order.
|
||||
void setPasses(std::vector<Pass> sortedPasses);
|
||||
|
||||
const char* getViewName() const;
|
||||
|
||||
const std::vector<Pass>& getPasses() const;
|
||||
|
||||
const std::unordered_map<ResourceId, Resource>& getResources() const;
|
||||
|
||||
private:
|
||||
utils::CString viewName;
|
||||
// The order of the passes in the vector indicates the execution
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "ApiHandler.h"
|
||||
|
||||
#include <fgviewer/DebugServer.h>
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
@@ -41,96 +40,29 @@ auto const error = [](int line, std::string const& uri) {
|
||||
|
||||
} // anonymous
|
||||
|
||||
bool ApiHandler::handleGetApiFgInfo(struct mg_connection* conn,
|
||||
struct mg_request_info const* request) {
|
||||
auto const softError = [conn, request](char const* msg) {
|
||||
utils::slog.e << "[fgviewer] DebugServer: " << msg << ": " << request->query_string << utils::io::endl;
|
||||
mg_printf(conn, kErrorHeader.data(), "application/txt");
|
||||
mg_write(conn, msg, strlen(msg));
|
||||
return true;
|
||||
};
|
||||
|
||||
// TODO: Implement the method
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApiHandler::addFrameGraph(FrameGraphInfo const* framegraph) {
|
||||
// TODO: Implement the method
|
||||
}
|
||||
|
||||
|
||||
bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) {
|
||||
struct mg_request_info const* request = mg_get_request_info(conn);
|
||||
std::string const& uri = request->local_uri;
|
||||
|
||||
if (uri.find("/api/status") == 0) {
|
||||
return handleGetStatus(conn, request);
|
||||
}
|
||||
|
||||
if (uri == "/api/framegraphs") {
|
||||
std::unique_lock const lock(mServer->mViewsMutex);
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "[");
|
||||
int index = 0;
|
||||
for (auto const& view: mServer->mViews) {
|
||||
bool const last = (++index) == mServer->mViews.size();
|
||||
|
||||
JsonWriter writer;
|
||||
if (!writer.writeFrameGraphInfo(view.second)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
mg_printf(conn, "{ \"fgid\": \"%8.8x\", %s } %s", view.first, writer.getJsonString(),
|
||||
last ? "" : ",");
|
||||
}
|
||||
mg_printf(conn, "]");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri == "/api/framegraph") {
|
||||
const FrameGraphInfo* result = getFrameGraphInfo(request);
|
||||
if (!result) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
JsonWriter writer;
|
||||
if (!writer.writeFrameGraphInfo(*result)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "{ %s }", writer.getJsonString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
void ApiHandler::updateFrameGraph(ViewHandle view_handle) {
|
||||
std::unique_lock const lock(mStatusMutex);
|
||||
snprintf(statusFrameGraphId, sizeof(statusFrameGraphId), "%8.8x", view_handle);
|
||||
mCurrentStatus++;
|
||||
mStatusCondition.notify_all();
|
||||
}
|
||||
|
||||
const FrameGraphInfo* ApiHandler::getFrameGraphInfo(struct mg_request_info const* request) {
|
||||
size_t const qlength = strlen(request->query_string);
|
||||
char fgid[9] = {};
|
||||
if (mg_get_var(request->query_string, qlength, "fgid", fgid, sizeof(fgid)) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
uint32_t const id = strtoul(fgid, nullptr, 16);
|
||||
std::unique_lock const lock(mServer->mViewsMutex);
|
||||
const auto it = mServer->mViews.find(id);
|
||||
return it == mServer->mViews.end()
|
||||
? nullptr
|
||||
: &(it->second);
|
||||
}
|
||||
|
||||
bool ApiHandler::handleGetStatus(struct mg_connection* conn,
|
||||
struct mg_request_info const* request) {
|
||||
char const* qstr = request->query_string;
|
||||
if (qstr && strcmp(qstr, "firstTime") == 0) {
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/txt");
|
||||
mg_write(conn, "0", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(mStatusMutex);
|
||||
uint64_t const currentStatusCount = mCurrentStatus;
|
||||
if (mStatusCondition.wait_for(lock, 10s,
|
||||
[this, currentStatusCount] {
|
||||
return currentStatusCount < mCurrentStatus;
|
||||
})) {
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/txt");
|
||||
mg_write(conn, statusFrameGraphId, 8);
|
||||
} else {
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/txt");
|
||||
// Use '1' to indicate a no-op. This ensures that we don't block forever if the client is
|
||||
// gone.
|
||||
mg_write(conn, "1", 1);
|
||||
}
|
||||
// TODO: Implement the method
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
#ifndef FGVIEWER_APIHANDLER_H
|
||||
#define FGVIEWER_APIHANDLER_H
|
||||
|
||||
#include <fgviewer/DebugServer.h>
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
@@ -29,8 +27,7 @@ struct FrameGraphInfo;
|
||||
// Handles the following REST requests, where {id} is an 8-digit hex string.
|
||||
//
|
||||
// GET /api/framegraphs
|
||||
// GET /api/framegraph?fg={fgid}
|
||||
// GET /api/status
|
||||
// GET /api/framegraph?fg={viewname}
|
||||
//
|
||||
class ApiHandler : public CivetHandler {
|
||||
public:
|
||||
@@ -39,25 +36,12 @@ public:
|
||||
~ApiHandler() = default;
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
|
||||
void updateFrameGraph(ViewHandle view_handle);
|
||||
void addFrameGraph(FrameGraphInfo const* frameGraph);
|
||||
|
||||
private:
|
||||
const FrameGraphInfo* getFrameGraphInfo(struct mg_request_info const* request);
|
||||
|
||||
bool handleGetStatus(struct mg_connection* conn,
|
||||
struct mg_request_info const* request);
|
||||
bool handleGetApiFgInfo(struct mg_connection* conn, struct mg_request_info const* request);
|
||||
|
||||
DebugServer* mServer;
|
||||
|
||||
std::mutex mStatusMutex;
|
||||
std::condition_variable mStatusCondition;
|
||||
char statusFrameGraphId[9] = {};
|
||||
|
||||
// This variable is to implement a *hanging* effect for /api/status. The call to /api/status
|
||||
// will always block until statusMaterialId is updated again. The client is expected to keep
|
||||
// calling /api/status (a constant "pull" to simulate a push).
|
||||
uint64_t mCurrentStatus = 0;
|
||||
};
|
||||
|
||||
} // filament::fgviewer
|
||||
|
||||
@@ -110,7 +110,6 @@ ViewHandle DebugServer::createView(utils::CString name) {
|
||||
std::unique_lock<utils::Mutex> lock(mViewsMutex);
|
||||
ViewHandle handle = mViewCounter++;
|
||||
mViews.emplace(handle, FrameGraphInfo(std::move(name)));
|
||||
mApiHandler->updateFrameGraph(handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -122,19 +121,8 @@ void DebugServer::destroyView(ViewHandle h) {
|
||||
|
||||
void DebugServer::update(ViewHandle h, FrameGraphInfo info) {
|
||||
std::unique_lock<utils::Mutex> lock(mViewsMutex);
|
||||
const auto it = mViews.find(h);
|
||||
if (it == mViews.end()) {
|
||||
slog.w << "[fgviewer] Received update for unknown handle " << h;
|
||||
return;
|
||||
}
|
||||
|
||||
bool has_changed = !(it->second == info);
|
||||
if (!has_changed)
|
||||
return;
|
||||
|
||||
mViews.erase(h);
|
||||
mViews.emplace(h, std::move(info));
|
||||
mApiHandler->updateFrameGraph(h);
|
||||
}
|
||||
|
||||
} // namespace filament::fgviewer
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
* Copyright (C) 2024 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.
|
||||
@@ -25,33 +25,15 @@ FrameGraphInfo::~FrameGraphInfo() = default;
|
||||
|
||||
FrameGraphInfo::FrameGraphInfo(FrameGraphInfo&& rhs) noexcept = default;
|
||||
|
||||
bool FrameGraphInfo::operator==(const FrameGraphInfo& rhs) const {
|
||||
return viewName == rhs.viewName
|
||||
&& passes == rhs.passes
|
||||
&& resources == rhs.resources;
|
||||
}
|
||||
|
||||
FrameGraphInfo::Pass::Pass(utils::CString name, std::vector<ResourceId> reads,
|
||||
std::vector<ResourceId> writes): name(std::move(name)),
|
||||
reads(std::move(reads)),
|
||||
writes(std::move(writes)) {}
|
||||
|
||||
bool FrameGraphInfo::Pass::operator==(const Pass& rhs) const {
|
||||
return name == rhs.name && reads == rhs.reads && writes == rhs.writes;
|
||||
}
|
||||
|
||||
FrameGraphInfo::Resource::Resource(ResourceId id, utils::CString name,
|
||||
std::vector<Property> properties): id(id),
|
||||
name(std::move(name)), properties(std::move(properties)) {}
|
||||
|
||||
bool FrameGraphInfo::Resource::operator==(const Resource& rhs) const {
|
||||
return id == rhs.id && name == rhs.name && properties == rhs.properties;
|
||||
}
|
||||
|
||||
bool FrameGraphInfo::Resource::Property::operator==(const Property &rhs) const {
|
||||
return name == rhs.name && value == rhs.value;
|
||||
}
|
||||
|
||||
void FrameGraphInfo::setResources(
|
||||
std::unordered_map<ResourceId, Resource> resources) {
|
||||
this->resources = std::move(resources);
|
||||
@@ -61,17 +43,4 @@ void FrameGraphInfo::setPasses(std::vector<Pass> sortedPasses) {
|
||||
passes = std::move(sortedPasses);
|
||||
}
|
||||
|
||||
const char* FrameGraphInfo::getViewName() const {
|
||||
return viewName.c_str_safe();
|
||||
}
|
||||
|
||||
const std::vector<FrameGraphInfo::Pass>& FrameGraphInfo::getPasses() const {
|
||||
return passes;
|
||||
}
|
||||
|
||||
const std::unordered_map<ResourceId, FrameGraphInfo::Resource>&
|
||||
FrameGraphInfo::getResources() const {
|
||||
return resources;
|
||||
}
|
||||
|
||||
} // namespace filament::fgviewer
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 <fgviewer/JsonWriter.h>
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
namespace {
|
||||
void writeJSONString(std::ostream& os, const char* str) {
|
||||
os << '"';
|
||||
const char* p = str;
|
||||
while (*p != '\0') {
|
||||
switch (*p) {
|
||||
case '"': os << "\\\""; break;
|
||||
case '\\': os << "\\\\"; break;
|
||||
case '\n': os << "\\n"; break;
|
||||
case '\t': os << "\\t"; break;
|
||||
default: os << *p; break;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
os << '"';
|
||||
}
|
||||
|
||||
void writeViewName(std::ostream& os, const FrameGraphInfo& frameGraph) {
|
||||
os << " \"viewName\": ";
|
||||
writeJSONString(os, frameGraph.getViewName());
|
||||
os << ",\n";
|
||||
}
|
||||
|
||||
void writeResourceIds(std::ostream& os, const std::vector<ResourceId>& resources) {
|
||||
for (size_t j = 0; j < resources.size(); ++j) {
|
||||
os << resources[j];
|
||||
if (j + 1 < resources.size()) os << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) {
|
||||
os << " \"passes\": [\n";
|
||||
auto& passes = frameGraph.getPasses();
|
||||
for (size_t i = 0; i < passes.size(); ++i) {
|
||||
const FrameGraphInfo::Pass& pass = passes[i];
|
||||
os << " {\n";
|
||||
os << " \"name\": ";
|
||||
writeJSONString(os, pass.name.c_str());
|
||||
os << ",\n";
|
||||
|
||||
const std::vector<ResourceId>& reads = pass.reads;
|
||||
os << " \"reads\": [";
|
||||
writeResourceIds(os, reads);
|
||||
os << "],\n";
|
||||
|
||||
const std::vector<ResourceId>& writes = pass.writes;
|
||||
os << " \"writes\": [";
|
||||
writeResourceIds(os, writes);
|
||||
os << "]\n";
|
||||
|
||||
os << " }";
|
||||
if (i + 1 < passes.size()) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ],\n";
|
||||
}
|
||||
|
||||
void writeResources(std::ostream& os, const FrameGraphInfo& frameGraph) {
|
||||
os << " \"resources\": {\n";
|
||||
size_t resourceCount = 0;
|
||||
auto& resources = frameGraph.getResources();
|
||||
for (const auto& [id, resource] : resources) {
|
||||
os << " \"" << id << "\": {\n";
|
||||
os << " \"id\": " << resource.id << ",\n";
|
||||
os << " \"name\": ";
|
||||
writeJSONString(os, resource.name.c_str());
|
||||
os << ",\n";
|
||||
|
||||
os << " \"properties\": [\n";
|
||||
for (size_t j = 0; j < resource.properties.size(); ++j) {
|
||||
const auto& [key, value] = resource.properties[j];
|
||||
os << " {\n";
|
||||
os << " \"key\": ";
|
||||
writeJSONString(os, key.c_str());
|
||||
os << ",\n";
|
||||
os << " \"value\": ";
|
||||
writeJSONString(os, value.c_str());
|
||||
os << "\n }";
|
||||
if (j + 1 < resource.properties.size()) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]\n";
|
||||
os << " }";
|
||||
if (++resourceCount < resources.size()) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " }\n";
|
||||
}
|
||||
} // anonymous
|
||||
|
||||
const char* JsonWriter::getJsonString() const {
|
||||
return mJsonString.c_str();
|
||||
}
|
||||
|
||||
size_t JsonWriter::getJsonSize() const {
|
||||
return mJsonString.size();
|
||||
}
|
||||
|
||||
bool JsonWriter::writeFrameGraphInfo(const FrameGraphInfo& frameGraph) {
|
||||
std::ostringstream os;
|
||||
|
||||
writeViewName(os, frameGraph);
|
||||
writePasses(os, frameGraph);
|
||||
writeResources(os, frameGraph);
|
||||
|
||||
|
||||
mJsonString = utils::CString(os.str().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace filament::fgviewer
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
// api.js encapsulates all the REST endpoints that the server provides
|
||||
|
||||
async function _fetchJson(uri) {
|
||||
const response = await fetch(uri);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function _fetchText(uri) {
|
||||
const response = await fetch(uri);
|
||||
return await response.text();
|
||||
}
|
||||
|
||||
async function fetchFrameGraphs() {
|
||||
const fgJson = await _fetchJson("api/framegraphs")
|
||||
const ret = {};
|
||||
for (const fgInfo of fgJson) {
|
||||
ret[fgInfo.fgid] = fgInfo;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async function fetchFrameGraph(fgid) {
|
||||
const fgInfo = await _fetchJson(`api/framegraph?fgid=${fgid}`);
|
||||
fgInfo.fgid = fgid;
|
||||
return fgInfo;
|
||||
}
|
||||
|
||||
const STATUS_LOOP_TIMEOUT = 3000;
|
||||
|
||||
const STATUS_CONNECTED = 1;
|
||||
const STATUS_DISCONNECTED = 2;
|
||||
const STATUS_FRAMEGRAPH_UPDATED = 3;
|
||||
|
||||
// Status function should be of the form function(status, data)
|
||||
async function statusLoop(isConnected, onStatus) {
|
||||
// This is a hanging get except for when transition from disconnected to connected, which
|
||||
// should return immediately.
|
||||
try {
|
||||
const fgid = await _fetchText("api/status" + (isConnected() ? '' : '?firstTime'));
|
||||
// A first-time request returned successfully
|
||||
if (fgid === '0') {
|
||||
onStatus(STATUS_CONNECTED);
|
||||
} else if (fgid !== '1') {
|
||||
onStatus(STATUS_FRAMEGRAPH_UPDATED, fgid);
|
||||
} // fgid == '1' is no-op, just loop again
|
||||
statusLoop(isConnected, onStatus);
|
||||
} catch {
|
||||
onStatus(STATUS_DISCONNECTED);
|
||||
setTimeout(() => statusLoop(isConnected, onStatus), STATUS_LOOP_TIMEOUT)
|
||||
}
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
import {LitElement, html, css, unsafeCSS, nothing} from "https://unpkg.com/lit@2.8.0?module";
|
||||
|
||||
const kUntitledPlaceholder = "untitled";
|
||||
|
||||
// CSS constants
|
||||
const FOREGROUND_COLOR = '#fafafa';
|
||||
const INACTIVE_COLOR = '#6f6f6f';
|
||||
const UNSELECTED_COLOR = '#dfdfdf';
|
||||
const BACKGROUND_COLOR = '#5362e5';
|
||||
const REGULAR_FONT_SIZE = 12;
|
||||
|
||||
// Constants for color operations
|
||||
const READ_COLOR = '#3ac224';
|
||||
const WRITE_COLOR = '#d43232';
|
||||
const NO_ACCESS_COLOR = '#d8dde754';
|
||||
const READ_WRITE_COLOR = '#ffeb99';
|
||||
const DEFAULT_COLOR = '#ffffff';
|
||||
const SUBRESOURCE_COLOR = '#d3d3d3';
|
||||
|
||||
const RESOURCE_USAGE_TYPE_READ = 'read';
|
||||
const RESOURCE_USAGE_TYPE_WRITE = 'write';
|
||||
const RESOURCE_USAGE_TYPE_NO_ACCESS = 'no-access';
|
||||
const RESOURCE_USAGE_TYPE_READ_WRITE = 'read-write';
|
||||
|
||||
const IS_SUBRESOURCE_KEY = 'is_subresource'
|
||||
|
||||
class MenuSection extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
showing: {type: Boolean, state: true},
|
||||
title: {type: String, attribute: 'title'},
|
||||
};
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
font-size: ${unsafeCSS(REGULAR_FONT_SIZE)}px;
|
||||
color: ${unsafeCSS(UNSELECTED_COLOR)};
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
color: ${unsafeCSS(UNSELECTED_COLOR)};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0px;
|
||||
border-top: 1px solid ${unsafeCSS(UNSELECTED_COLOR)};
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin: 3px 0 8px 0;
|
||||
}
|
||||
|
||||
.expander {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
_showClick() {
|
||||
this.showing = !this.showing;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.showing = true;
|
||||
}
|
||||
|
||||
render() {
|
||||
const expandedIcon = this.showing ? '-' : '+';
|
||||
const slot = (() => html`
|
||||
<slot></slot>`)();
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="section-title expander" @click="${this._showClick}">
|
||||
<span>${this.title}</span> <span>${expandedIcon}</span>
|
||||
</div>
|
||||
<hr/>
|
||||
${this.showing ? slot : []}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('menu-section', MenuSection);
|
||||
|
||||
class FrameGraphSidePanel extends LitElement {
|
||||
// Setting the style in render() has poor performance implications. We use it simply to avoid
|
||||
// having another container descending from the root to host the background color.
|
||||
dynamicStyle() {
|
||||
return `
|
||||
:host {
|
||||
position: fixed;
|
||||
background: ${this.connected ? BACKGROUND_COLOR : INACTIVE_COLOR};
|
||||
width: 20%;
|
||||
height: 100%;
|
||||
max-width: 250px;
|
||||
min-width: 250px;
|
||||
padding: 10px 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.title {
|
||||
color: white;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
.framegraphs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
font-size: ${REGULAR_FONT_SIZE}px;
|
||||
color: ${UNSELECTED_COLOR};
|
||||
}
|
||||
.framegraph:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.framegraph {
|
||||
cursor: pointer;
|
||||
}
|
||||
.selected {
|
||||
font-weight: bolder;
|
||||
color: ${FOREGROUND_COLOR};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
connected: {type: Boolean, attribute: 'connected'},
|
||||
currentFrameGraph: {type: String, attribute: 'current-framegraph'},
|
||||
|
||||
database: {type: Object, state: true},
|
||||
framegraphs: {type: Array, state: true},
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.connected = false;
|
||||
this.framegraphs = [];
|
||||
this.database = {};
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('database')) {
|
||||
const items = [];
|
||||
// Names need not be unique, so we display a numeric suffix for non-unique names.
|
||||
// To achieve stable ordering of anonymous framegraphs, we first sort by fgid.
|
||||
const labels = new Set();
|
||||
const fgids = Object.keys(this.database).sort();
|
||||
const duplicatedLabels = {};
|
||||
for (const fgid of fgids) {
|
||||
const name = this.database[fgid].viewName || kUntitledPlaceholder;
|
||||
if (labels.has(name)) {
|
||||
duplicatedLabels[name] = 0;
|
||||
} else {
|
||||
labels.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
this.framegraphs = fgids.map((fgid) => {
|
||||
const framegraph = this.database[fgid];
|
||||
let name = framegraph.viewName || kUntitledPlaceholder;
|
||||
if (name in duplicatedLabels) {
|
||||
const index = duplicatedLabels[name];
|
||||
duplicatedLabels[name] = index + 1;
|
||||
name = `${name} (${index})`;
|
||||
}
|
||||
return {
|
||||
fgid: fgid,
|
||||
name: name,
|
||||
domain: "views"
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_handleFrameGraphClick(ev) {
|
||||
this.dispatchEvent(new CustomEvent('select-framegraph', {
|
||||
detail: ev,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const sections = (title) => {
|
||||
const fgs = this.framegraphs
|
||||
.map((fg) => {
|
||||
const framegraph = this.database[fg.fgid];
|
||||
const onClick = this._handleFrameGraphClick.bind(this, fg.fgid);
|
||||
const isFrameGraphSelected = fg.fgid === this.currentFrameGraph;
|
||||
const fgName = (isFrameGraphSelected ? '● ' : '') + framegraph.viewName;
|
||||
return html`
|
||||
<div class="framegraph" @click="${onClick}" data-id="${fg.fgid}">
|
||||
${fgName}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
if (fgs.length > 0) {
|
||||
return html`
|
||||
<menu-section title="${title}">${fgs}</menu-section>`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return html`
|
||||
<style>${this.dynamicStyle()}</style>
|
||||
<div class="container">
|
||||
<div class="title">fgviewer</div>
|
||||
${sections("Views", "views") ?? nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define("framegraph-sidepanel", FrameGraphSidePanel);
|
||||
|
||||
class FrameGraphTable extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid #ddd;
|
||||
margin-left: 300px;
|
||||
}
|
||||
|
||||
.scrollable-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollable-table td,
|
||||
.scrollable-table th {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.scrollable-table tr {
|
||||
position: sticky;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sticky-col {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
th {
|
||||
min-width: 100px;
|
||||
background-color: #f2f2f2;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
frameGraphData: {type: Object, state: true}, // Expecting a JSON frame graph structure
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.frameGraphData = null;
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('frameGraphData')) {
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
_getCellColor(type, defaultColor) {
|
||||
switch (type) {
|
||||
case RESOURCE_USAGE_TYPE_READ:
|
||||
return READ_COLOR;
|
||||
case RESOURCE_USAGE_TYPE_WRITE:
|
||||
return WRITE_COLOR;
|
||||
case RESOURCE_USAGE_TYPE_NO_ACCESS:
|
||||
return NO_ACCESS_COLOR;
|
||||
case RESOURCE_USAGE_TYPE_READ_WRITE:
|
||||
return READ_WRITE_COLOR;
|
||||
default:
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
_toggleCollapse(resourceIndex) {
|
||||
const subresourceRows = this.shadowRoot.querySelectorAll(`[id^="subresource-${resourceIndex}-"]`);
|
||||
const icon = this.shadowRoot.querySelector(`#resource-${resourceIndex} .toggle-icon`);
|
||||
const isHidden = subresourceRows[0]?.classList.contains('hidden');
|
||||
subresourceRows.forEach(row => row.classList.toggle('hidden'));
|
||||
icon.textContent = isHidden ? '▼' : '▶';
|
||||
}
|
||||
|
||||
_getRowHtml(allPasses, resourceId, defaultColor) {
|
||||
return allPasses.map((passData, index) => {
|
||||
const isRead = passData?.reads.includes(resourceId);
|
||||
const isWrite = passData?.writes.includes(resourceId);
|
||||
let type = null;
|
||||
const hasUsed = (passData) => {
|
||||
return passData?.reads.includes(resourceId) || passData?.writes.includes(resourceId);
|
||||
};
|
||||
const hasBeenUsedBefore = allPasses.slice(0, index).some(hasUsed);
|
||||
const willBeUsedLater = allPasses.slice(index + 1).some(hasUsed);
|
||||
|
||||
if (isRead && isWrite) type = RESOURCE_USAGE_TYPE_READ_WRITE;
|
||||
else if (isRead) type = RESOURCE_USAGE_TYPE_READ;
|
||||
else if (isWrite) type = RESOURCE_USAGE_TYPE_WRITE;
|
||||
else if (hasBeenUsedBefore && willBeUsedLater) type = RESOURCE_USAGE_TYPE_NO_ACCESS;
|
||||
return html`
|
||||
<td style="background-color: ${unsafeCSS(this._getCellColor(type, defaultColor))};">
|
||||
${type ?? nothing}
|
||||
</td>`;
|
||||
});
|
||||
}
|
||||
|
||||
_isSubresourceOfParent(resource, parentResource){
|
||||
return resource.properties?.some(prop =>
|
||||
prop.key === IS_SUBRESOURCE_KEY &&
|
||||
Number(prop.value) === parentResource.id)
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.frameGraphData || !this.frameGraphData.passes || !this.frameGraphData.resources) return nothing;
|
||||
const allPasses = this.frameGraphData.passes;
|
||||
const resources = Object.values(this.frameGraphData.resources);
|
||||
return html`
|
||||
<div class="table-container">
|
||||
<table class="scrollable-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-col">Resources/Passes</th>
|
||||
${allPasses.map(pass => html`
|
||||
<th>${pass.name}</th>`)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${resources.map((resource, resourceIndex) => {
|
||||
const isSubresource = resource.properties?.some(prop => prop.key === IS_SUBRESOURCE_KEY);
|
||||
if (isSubresource) return nothing;
|
||||
|
||||
const hasSubresources = resources.some(subresource => this._isSubresourceOfParent(subresource, resource));
|
||||
return html`
|
||||
<tr id="resource-${resourceIndex}">
|
||||
<th class="sticky-col">
|
||||
${hasSubresources ? html`
|
||||
<span
|
||||
class="toggle-icon"
|
||||
@click="${() => this._toggleCollapse(resourceIndex)}"
|
||||
>▶</span>` : nothing}
|
||||
${resource.name}
|
||||
</th>
|
||||
${this._getRowHtml(allPasses, resource.id, DEFAULT_COLOR)}
|
||||
</tr>
|
||||
${resources.filter(subresource => this._isSubresourceOfParent(subresource, resource)
|
||||
).map((subresource, subIndex) => html`
|
||||
<tr id="subresource-${resourceIndex}-${subIndex}"
|
||||
class="collapsible hidden">
|
||||
<td class="sticky-col"
|
||||
style="background-color: ${SUBRESOURCE_COLOR}">
|
||||
${subresource.name}
|
||||
</td>
|
||||
${this._getRowHtml(allPasses, subresource.id, SUBRESOURCE_COLOR)}
|
||||
</tr>
|
||||
`)}
|
||||
`;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("framegraph-table", FrameGraphTable);
|
||||
|
||||
class FrameGraphViewer extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
get _sidePanel() {
|
||||
return this.renderRoot.querySelector('#sidepanel');
|
||||
}
|
||||
|
||||
get _framegraphTable() {
|
||||
return this.renderRoot.querySelector('#table');
|
||||
}
|
||||
|
||||
async init() {
|
||||
const isConnected = () => this.connected;
|
||||
statusLoop(
|
||||
isConnected,
|
||||
async (status, fgid) => {
|
||||
this.connected = status == STATUS_CONNECTED || status == STATUS_FRAMEGRAPH_UPDATED;
|
||||
|
||||
if (status == STATUS_FRAMEGRAPH_UPDATED) {
|
||||
let fgInfo = await fetchFrameGraph(fgid);
|
||||
this.database[fgInfo.fgid] = fgInfo;
|
||||
this._framegraphTable.frameGraphData = fgInfo;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let framegraphs = await fetchFrameGraphs();
|
||||
this.database = framegraphs;
|
||||
}
|
||||
|
||||
_getFrameGraph() {
|
||||
const framegraph = (this.database && this.currentFrameGraph) ?
|
||||
this.database[this.currentFrameGraph] : null;
|
||||
return framegraph;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.connected = false;
|
||||
this.database = {};
|
||||
this.currentFrameGraph = null;
|
||||
this.init();
|
||||
|
||||
this.addEventListener('select-framegraph',
|
||||
(ev) => {
|
||||
this.currentFrameGraph = ev.detail;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
connected: {type: Boolean, state: true},
|
||||
database: {type: Object, state: true},
|
||||
currentFrameGraph: {type: String, state: true},
|
||||
}
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('currentFrameGraph') || props.has('database')) {
|
||||
const framegraph = this._getFrameGraph();
|
||||
this._framegraphTable.frameGraphData = framegraph;
|
||||
this._sidePanel.database = this.database;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<framegraph-sidepanel id="sidepanel"
|
||||
?connected="${this.connected}"
|
||||
current-framegraph="${this.currentFrameGraph}" >
|
||||
</framegraph-sidepanel>
|
||||
<framegraph-table id="table"
|
||||
?connected="${this.connected}"
|
||||
current-framegraph="${this.currentFrameGraph}"
|
||||
</framegraph-table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("framegraph-viewer", FrameGraphViewer);
|
||||
@@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Filament Debugger</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
</style>
|
||||
<script src="api.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js" type="module"></script>
|
||||
<framegraph-viewer></framegraph-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -144,17 +144,19 @@ install(DIRECTORY ${PUBLIC_HDR_DIR}/filamat DESTINATION include)
|
||||
# ==================================================================================================
|
||||
# Tests
|
||||
# ==================================================================================================
|
||||
if (IS_HOST_PLATFORM)
|
||||
project(test_filamat)
|
||||
set(TARGET test_filamat)
|
||||
set(SRCS
|
||||
tests/test_filamat.cpp
|
||||
tests/test_argBufferFixup.cpp
|
||||
tests/test_clipDistanceFixup.cpp
|
||||
tests/test_includes.cpp)
|
||||
project(test_filamat)
|
||||
set(TARGET test_filamat)
|
||||
set(SRCS
|
||||
tests/test_filamat.cpp
|
||||
tests/test_argBufferFixup.cpp
|
||||
tests/test_clipDistanceFixup.cpp
|
||||
tests/test_includes.cpp)
|
||||
|
||||
add_executable(${TARGET} ${SRCS})
|
||||
|
||||
target_include_directories(${TARGET} PRIVATE src)
|
||||
|
||||
target_link_libraries(${TARGET} filamat gtest)
|
||||
|
||||
set_target_properties(${TARGET} PROPERTIES FOLDER Tests)
|
||||
|
||||
add_executable(${TARGET} ${SRCS})
|
||||
target_include_directories(${TARGET} PRIVATE src)
|
||||
target_link_libraries(${TARGET} filamat gtest)
|
||||
set_target_properties(${TARGET} PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
|
||||
@@ -34,54 +34,44 @@
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t FAKE_DRY_RUNNER_START_ADDR = 0x1;
|
||||
} // anonymous
|
||||
|
||||
namespace filamat {
|
||||
|
||||
class Flattener {
|
||||
public:
|
||||
explicit Flattener(uint8_t* dst) : mCursor(dst), mStart(dst){}
|
||||
|
||||
// DryRunner is used to compute the size of the flattened output but not actually carry out
|
||||
// flattening. If we set mStart = nullptr and mEnd=nullptr, we would hit an error about
|
||||
// offsetting on null when ubsan is enabled. Instead we point mStart to a fake address, and
|
||||
// mCursor is offset from that.
|
||||
static Flattener& getDryRunner() {
|
||||
static Flattener dryRunner = Flattener(nullptr);
|
||||
dryRunner.mStart = (uint8_t*) FAKE_DRY_RUNNER_START_ADDR;
|
||||
dryRunner.mCursor = (uint8_t*) FAKE_DRY_RUNNER_START_ADDR;
|
||||
dryRunner.mStart = nullptr;
|
||||
dryRunner.mCursor = nullptr;
|
||||
dryRunner.mOffsetPlaceholders.clear();
|
||||
dryRunner.mSizePlaceholders.clear();
|
||||
dryRunner.mValuePlaceholders.clear();
|
||||
return dryRunner;
|
||||
}
|
||||
|
||||
bool isDryRunner() {
|
||||
return mStart == (uint8_t*) FAKE_DRY_RUNNER_START_ADDR;
|
||||
}
|
||||
bool isDryRunner() { return mStart == nullptr;}
|
||||
|
||||
size_t getBytesWritten() {
|
||||
return mCursor - mStart;
|
||||
}
|
||||
|
||||
void writeBool(bool b) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = static_cast<uint8_t>(b);
|
||||
}
|
||||
mCursor += 1;
|
||||
}
|
||||
|
||||
void writeUint8(uint8_t i) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = i;
|
||||
}
|
||||
mCursor += 1;
|
||||
}
|
||||
|
||||
void writeUint16(uint16_t i) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = static_cast<uint8_t>( i & 0xff);
|
||||
mCursor[1] = static_cast<uint8_t>((i >> 8) & 0xff);
|
||||
}
|
||||
@@ -89,7 +79,7 @@ public:
|
||||
}
|
||||
|
||||
void writeUint32(uint32_t i) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = static_cast<uint8_t>( i & 0xff);
|
||||
mCursor[1] = static_cast<uint8_t>((i >> 8) & 0xff);
|
||||
mCursor[2] = static_cast<uint8_t>((i >> 16) & 0xff);
|
||||
@@ -99,7 +89,7 @@ public:
|
||||
}
|
||||
|
||||
void writeUint64(uint64_t i) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = static_cast<uint8_t>( i & 0xff);
|
||||
mCursor[1] = static_cast<uint8_t>((i >> 8) & 0xff);
|
||||
mCursor[2] = static_cast<uint8_t>((i >> 16) & 0xff);
|
||||
@@ -114,7 +104,7 @@ public:
|
||||
|
||||
void writeString(const char* str) {
|
||||
size_t const len = strlen(str);
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
strcpy(reinterpret_cast<char*>(mCursor), str);
|
||||
}
|
||||
mCursor += len + 1;
|
||||
@@ -122,7 +112,7 @@ public:
|
||||
|
||||
void writeString(std::string_view str) {
|
||||
size_t const len = str.length();
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
memcpy(reinterpret_cast<char*>(mCursor), str.data(), len);
|
||||
mCursor[len] = 0;
|
||||
}
|
||||
@@ -131,14 +121,14 @@ public:
|
||||
|
||||
void writeBlob(const char* blob, size_t nbytes) {
|
||||
writeUint64(nbytes);
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
memcpy(reinterpret_cast<char*>(mCursor), blob, nbytes);
|
||||
}
|
||||
mCursor += nbytes;
|
||||
}
|
||||
|
||||
void writeRaw(const char* raw, size_t nbytes) {
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
memcpy(reinterpret_cast<char*>(mCursor), raw, nbytes);
|
||||
}
|
||||
mCursor += nbytes;
|
||||
@@ -146,7 +136,7 @@ public:
|
||||
|
||||
void writeSizePlaceholder() {
|
||||
mSizePlaceholders.push_back(mCursor);
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = 0;
|
||||
mCursor[1] = 0;
|
||||
mCursor[2] = 0;
|
||||
@@ -174,7 +164,7 @@ public:
|
||||
mSizePlaceholders.pop_back();
|
||||
// -4 to account for the 4 bytes we are about to write.
|
||||
uint32_t const size = static_cast<uint32_t>(mCursor - dst - 4);
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
dst[0] = static_cast<uint8_t>( size & 0xff);
|
||||
dst[1] = static_cast<uint8_t>((size >> 8) & 0xff);
|
||||
dst[2] = static_cast<uint8_t>((size >> 16) & 0xff);
|
||||
@@ -185,7 +175,7 @@ public:
|
||||
|
||||
void writeOffsetplaceholder(size_t index) {
|
||||
mOffsetPlaceholders.insert(std::pair<size_t, uint8_t*>(index, mCursor));
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = 0x0;
|
||||
mCursor[1] = 0x0;
|
||||
mCursor[2] = 0x0;
|
||||
@@ -195,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
void writeOffsets(uint32_t forIndex) {
|
||||
if (isDryRunner()) {
|
||||
if (mStart == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -219,7 +209,7 @@ public:
|
||||
|
||||
void writeValuePlaceholder() {
|
||||
mValuePlaceholders.push_back(mCursor);
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
mCursor[0] = 0;
|
||||
mCursor[1] = 0;
|
||||
mCursor[2] = 0;
|
||||
@@ -238,7 +228,7 @@ public:
|
||||
|
||||
uint8_t* dst = mValuePlaceholders.back();
|
||||
mValuePlaceholders.pop_back();
|
||||
if (!isDryRunner()) {
|
||||
if (mStart != nullptr) {
|
||||
dst[0] = static_cast<uint8_t>( v & 0xff);
|
||||
dst[1] = static_cast<uint8_t>((v >> 8) & 0xff);
|
||||
dst[2] = static_cast<uint8_t>((v >> 16) & 0xff);
|
||||
|
||||
@@ -295,12 +295,35 @@ utils::io::sstream& CodeGenerator::generateCommonProlog(utils::io::sstream& out,
|
||||
generateSpecializationConstant(out, "BACKEND_FEATURE_LEVEL",
|
||||
+ReservedSpecializationConstants::BACKEND_FEATURE_LEVEL, 1);
|
||||
|
||||
generateSpecializationConstant(out, "CONFIG_MAX_INSTANCES",
|
||||
+ReservedSpecializationConstants::CONFIG_MAX_INSTANCES, (int)CONFIG_MAX_INSTANCES);
|
||||
if (mTargetApi == TargetApi::VULKAN) {
|
||||
// Note: This is a hack for a hack.
|
||||
//
|
||||
// Vulkan doesn't support sizing arrays within a block with specialization constants,
|
||||
// as per this paragraph of the ARB_spir_v specification:
|
||||
// https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gl_spirv.txt
|
||||
//
|
||||
// Arrays inside a block may be sized with a specialization constant,
|
||||
// but the block will have a static layout. Changing the specialized size will
|
||||
// not re-layout the block. In the absence of explicit offsets, the layout will be
|
||||
// based on the default size of the array.
|
||||
//
|
||||
// CONFIG_MAX_INSTANCES is only needed for WebGL, so we can replace it with a constant.
|
||||
// CONFIG_FROXEL_BUFFER_HEIGHT can be hardcoded to 2048 because only 3% of Android devices
|
||||
// only support 16KiB buffer or less (1024 lines).
|
||||
//
|
||||
// We *could* leave these as a specialization constant, but this triggers a crashing bug with
|
||||
// some Adreno drivers on Android. see: https://github.com/google/filament/issues/6444
|
||||
//
|
||||
out << "const int CONFIG_MAX_INSTANCES = " << (int)CONFIG_MAX_INSTANCES << ";\n";
|
||||
out << "const int CONFIG_FROXEL_BUFFER_HEIGHT = 2048;\n";
|
||||
} else {
|
||||
generateSpecializationConstant(out, "CONFIG_MAX_INSTANCES",
|
||||
+ReservedSpecializationConstants::CONFIG_MAX_INSTANCES, (int)CONFIG_MAX_INSTANCES);
|
||||
|
||||
// the default of 1024 (16KiB) is needed for 32% of Android devices
|
||||
generateSpecializationConstant(out, "CONFIG_FROXEL_BUFFER_HEIGHT",
|
||||
+ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, 1024);
|
||||
// the default of 1024 (16KiB) is needed for 32% of Android devices
|
||||
generateSpecializationConstant(out, "CONFIG_FROXEL_BUFFER_HEIGHT",
|
||||
+ReservedSpecializationConstants::CONFIG_FROXEL_BUFFER_HEIGHT, 1024);
|
||||
}
|
||||
|
||||
// directional shadowmap visualization
|
||||
generateSpecializationConstant(out, "CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP",
|
||||
|
||||
@@ -988,7 +988,6 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
vbb.enableBufferObjects();
|
||||
|
||||
bool hasUv0 = false, hasUv1 = false, hasVertexColor = false, hasNormals = false;
|
||||
int8_t currentCustomIndex = -1;
|
||||
uint32_t vertexCount = 0;
|
||||
|
||||
const size_t firstSlot = slots->size();
|
||||
@@ -999,7 +998,6 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
const int index = attribute.index;
|
||||
const cgltf_attribute_type atype = attribute.type;
|
||||
const cgltf_accessor* accessor = attribute.data;
|
||||
int8_t customIndex = -1;
|
||||
|
||||
// The glTF tangent data is ignored here, but honored in ResourceLoader.
|
||||
if (atype == cgltf_attribute_type_tangent) {
|
||||
@@ -1017,19 +1015,12 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
}
|
||||
|
||||
if (atype == cgltf_attribute_type_color) {
|
||||
if (hasVertexColor) {
|
||||
// We already had a vertex color before, we need to store this is as a custom
|
||||
// attribute.
|
||||
customIndex = ++currentCustomIndex;
|
||||
} else {
|
||||
hasVertexColor = true;
|
||||
}
|
||||
hasVertexColor = true;
|
||||
}
|
||||
|
||||
// Translate the cgltf attribute enum into a Filament enum.
|
||||
VertexAttribute semantic;
|
||||
if (!getCustomVertexAttrType(customIndex, &semantic) &&
|
||||
!getVertexAttrType(atype, &semantic)) {
|
||||
if (!getVertexAttrType(atype, &semantic)) {
|
||||
utils::slog.e << "Unrecognized vertex semantic in " << name << utils::io::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -98,15 +98,6 @@ inline bool getVertexAttrType(cgltf_attribute_type atype, filament::VertexAttrib
|
||||
}
|
||||
}
|
||||
|
||||
inline bool getCustomVertexAttrType(int8_t customIndex, filament::VertexAttribute* attrType) {
|
||||
if (customIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
*attrType = static_cast<filament::VertexAttribute>(
|
||||
customIndex + (uint8_t) filament::VertexAttribute::CUSTOM0);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool getIndexType(cgltf_component_type ctype, filament::IndexBuffer::IndexType* itype) {
|
||||
switch (ctype) {
|
||||
case cgltf_component_type_r_8u:
|
||||
|
||||
@@ -449,6 +449,9 @@ bool ResourceLoader::loadResources(FFilamentAsset* asset, bool async) {
|
||||
// that we need to generate the contents of a GPU buffer by processing one or more CPU
|
||||
// buffer(s).
|
||||
pImpl->computeTangents(asset);
|
||||
|
||||
std::get<FFilamentAsset::ResourceInfo>(asset->mResourceInfo).mBufferSlots.clear();
|
||||
std::get<FFilamentAsset::ResourceInfo>(asset->mResourceInfo).mPrimitives.clear();
|
||||
} else {
|
||||
auto& slots = std::get<FFilamentAsset::ResourceInfoExtended>(asset->mResourceInfo).slots;
|
||||
ResourceLoaderExtended::loadResources(slots, pImpl->mEngine, asset->mBufferObjects);
|
||||
|
||||
@@ -240,6 +240,25 @@ function(add_demo NAME)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_demo2 NAME)
|
||||
include_directories(${GENERATION_ROOT})
|
||||
if (APPLE)
|
||||
add_executable(${NAME} helper.h helper.mm ${NAME}.cpp)
|
||||
target_link_libraries(${NAME} PRIVATE sample-resources)
|
||||
target_compile_options(${NAME} PRIVATE ${COMPILER_FLAGS})
|
||||
set_target_properties(${NAME} PROPERTIES FOLDER Samples)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
target_link_libraries(${NAME} PUBLIC "-framework Cocoa -framework QuartzCore" sdl2)
|
||||
set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(${NAME} PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
else()
|
||||
add_demo(${NAME})
|
||||
endif()
|
||||
target_link_libraries(${NAME} PRIVATE gltfio)
|
||||
endfunction()
|
||||
|
||||
|
||||
if (NOT ANDROID)
|
||||
add_demo(animation)
|
||||
add_demo(depthtesting)
|
||||
@@ -249,7 +268,7 @@ if (NOT ANDROID)
|
||||
add_demo(heightfield)
|
||||
add_demo(hellomorphing)
|
||||
add_demo(hellopbr)
|
||||
add_demo(hellotriangle)
|
||||
add_demo2(hellotriangle)
|
||||
add_demo(helloskinning)
|
||||
add_demo(helloskinningbuffer)
|
||||
add_demo(helloskinningbuffer_morebones)
|
||||
|
||||
@@ -1,192 +1,191 @@
|
||||
/*
|
||||
* 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 <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 "generated/resources/resources.h"
|
||||
#if defined(__linux__)
|
||||
void* getNativeWindow(SDL_Window* sdlWindow) {
|
||||
SDL_SysWMinfo wmi;
|
||||
SDL_VERSION(&wmi.version);
|
||||
SDL_GetWindowWMInfo(sdlWindow, &wmi);
|
||||
if (wmi.subsystem == SDL_SYSWM_X11) {
|
||||
Window win = (Window) wmi.info.x11.window;
|
||||
return (void*) win;
|
||||
} else {
|
||||
std::cout << "Unknown SDL subsystem";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include "helper.h"
|
||||
|
||||
#endif
|
||||
|
||||
SDL_Window* createSDLwindow() {
|
||||
uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
|
||||
SDL_Window* win = SDL_CreateWindow("Hello World!", 100, 100, 600, 400, 0);
|
||||
if (win == nullptr) {
|
||||
std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
|
||||
SDL_Quit();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
using namespace filament;
|
||||
using utils::Entity;
|
||||
using utils::EntityManager;
|
||||
|
||||
struct App {
|
||||
Config config;
|
||||
VertexBuffer* vb;
|
||||
IndexBuffer* ib;
|
||||
Material* mat;
|
||||
Camera* cam;
|
||||
Entity camera;
|
||||
Skybox* skybox;
|
||||
Entity renderable;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
filament::math::float2 position;
|
||||
uint32_t color;
|
||||
};
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };
|
||||
|
||||
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);
|
||||
using namespace math;
|
||||
using namespace utils;
|
||||
int main() {
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << usage;
|
||||
}
|
||||
const static uint32_t indices[] = { 0, 1, 2, };
|
||||
|
||||
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 }
|
||||
const static math::float3 vertices[] = {
|
||||
{ -10, 0, -10 },
|
||||
{ -10, 0, 10 },
|
||||
{ 10, 0, 10 },
|
||||
};
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
const static math::short4 normals[]{ tbn, tbn, tbn };
|
||||
SDL_Window* window = createSDLwindow();
|
||||
if (!window) {
|
||||
return 1;
|
||||
}
|
||||
Engine* engine = Engine::create(filament::backend::Backend::VULKAN);
|
||||
SwapChain* swapChain = engine->createSwapChain(getNativeWindow(window));
|
||||
Renderer* renderer = engine->createRenderer();
|
||||
|
||||
auto cameraEntity = EntityManager::get().create();
|
||||
Camera* camera = engine->createCamera(cameraEntity);
|
||||
View* view = engine->createView();
|
||||
Scene* scene = engine->createScene();
|
||||
|
||||
view->setCamera(camera);
|
||||
// Determine the current size of the window in physical pixels.
|
||||
uint32_t w, h;
|
||||
SDL_GL_GetDrawableSize(window, (int*) &w, (int*) &h);
|
||||
camera->lookAt(float3(0, 50.5f, 0), float3(0, 0, 0), float3(1.f, 0, 0));
|
||||
camera->setProjection(45.0, double(w) / h, 0.1, 50, Camera::Fov::VERTICAL);
|
||||
view->setViewport({ 0, 0, w, h });
|
||||
view->setScene(scene);
|
||||
view->setPostProcessingEnabled(false);
|
||||
|
||||
VertexBuffer* vertexBuffer =
|
||||
VertexBuffer::Builder()
|
||||
.vertexCount(3)
|
||||
.bufferCount(2)
|
||||
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3)
|
||||
.attribute(VertexAttribute::TANGENTS, 1, VertexBuffer::AttributeType::SHORT4)
|
||||
.normalized(VertexAttribute::TANGENTS)
|
||||
.build(*engine);
|
||||
|
||||
vertexBuffer->setBufferAt(*engine, 0,
|
||||
VertexBuffer::BufferDescriptor(vertices,
|
||||
vertexBuffer->getVertexCount() * sizeof(vertices[0])));
|
||||
vertexBuffer->setBufferAt(*engine, 1,
|
||||
VertexBuffer::BufferDescriptor(normals,
|
||||
vertexBuffer->getVertexCount() * sizeof(normals[0])));
|
||||
|
||||
IndexBuffer* indexBuffer = IndexBuffer::Builder().indexCount(3).build(*engine);
|
||||
|
||||
indexBuffer->setBuffer(*engine,
|
||||
IndexBuffer::BufferDescriptor(indices,
|
||||
indexBuffer->getIndexCount() * sizeof(uint32_t)));
|
||||
|
||||
filamat::MaterialBuilder::init();
|
||||
filamat::MaterialBuilder builder;
|
||||
builder.name("Material")
|
||||
.material(" void material(inout MaterialInputs material) {\n"
|
||||
" prepareMaterial(material);"
|
||||
" material.baseColor.rgb = materialParams.baseColor;"
|
||||
" }")
|
||||
.parameter("baseColor", filament::backend::UniformType::FLOAT3)
|
||||
.parameter("metallic", filament::backend::UniformType::FLOAT)
|
||||
.parameter("roughness", filament::backend::UniformType::FLOAT)
|
||||
.parameter("reflectance", filament::backend::UniformType::FLOAT)
|
||||
.optimization(filamat::MaterialBuilder::Optimization::NONE)
|
||||
.shading(filamat::MaterialBuilder::Shading::UNLIT)
|
||||
.targetApi(filamat::MaterialBuilder::TargetApi::ALL)
|
||||
.platform(filamat::MaterialBuilder::Platform::ALL);
|
||||
|
||||
filamat::Package package = builder.build(engine->getJobSystem());
|
||||
|
||||
Material* material =
|
||||
Material::Builder().package(package.getData(), package.getSize()).build(*engine);
|
||||
material->setDefaultParameter("baseColor", RgbType::LINEAR, float3{ 1, 0, 0 });
|
||||
material->setDefaultParameter("metallic", 0.0f);
|
||||
material->setDefaultParameter("roughness", 0.4f);
|
||||
material->setDefaultParameter("reflectance", 0.5f);
|
||||
|
||||
MaterialInstance* materialInstance = material->createInstance();
|
||||
Entity renderable = EntityManager::get().create();
|
||||
|
||||
// build a quad
|
||||
RenderableManager::Builder(1)
|
||||
.boundingBox({ { -1, -1, -1 }, { 1, 1, 1 } })
|
||||
.material(0, materialInstance)
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer, 0,
|
||||
3)
|
||||
.culling(false)
|
||||
.build(*engine, renderable);
|
||||
scene->addEntity(renderable);
|
||||
|
||||
int i = 0;
|
||||
while (i++ < 1) {
|
||||
// beginFrame() returns false if we need to skip a frame
|
||||
if (renderer->beginFrame(swapChain)) {
|
||||
// for each View
|
||||
renderer->render(view);
|
||||
renderer->endFrame();
|
||||
}
|
||||
}
|
||||
return optind;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
App app{};
|
||||
app.config.title = "hellotriangle";
|
||||
app.config.featureLevel = backend::FeatureLevel::FEATURE_LEVEL_0;
|
||||
handleCommandLineArguments(argc, argv, &app);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
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 }));
|
||||
});
|
||||
|
||||
FilamentApp::get().run(app.config, setup, cleanup);
|
||||
bool running = true;
|
||||
SDL_Event event;
|
||||
|
||||
while (running) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
running = false;
|
||||
}
|
||||
SDL_Delay(16);
|
||||
}
|
||||
}
|
||||
|
||||
engine->destroy(cameraEntity);
|
||||
return 0;
|
||||
}
|
||||
|
||||
10
samples/helper.h
Normal file
10
samples/helper.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef HELLO_TRIANGLE_H
|
||||
#define HELLO_TRIANGLE_H
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL_syswm.h>
|
||||
#include <SDL_video.h>
|
||||
|
||||
void* getNativeWindow(SDL_Window* sdlWindow);
|
||||
|
||||
#endif
|
||||
39
samples/helper.mm
Normal file
39
samples/helper.mm
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "helper.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <QuartzCore/QuartzCore.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
void* getNativeWindow(SDL_Window* sdlWindow) {
|
||||
SDL_SysWMinfo wmi;
|
||||
SDL_VERSION(&wmi.version);
|
||||
FILAMENT_CHECK_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi))
|
||||
<< "SDL version unsupported!";
|
||||
NSWindow* win = wmi.info.cocoa.window;
|
||||
NSView* view = [win contentView];
|
||||
|
||||
[win setColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
|
||||
[view setWantsLayer:YES];
|
||||
CAMetalLayer* metalLayer = [CAMetalLayer layer];
|
||||
metalLayer.bounds = view.bounds;
|
||||
|
||||
// It's important to set the drawableSize to the actual backing pixels. When rendering
|
||||
// full-screen, we can skip the macOS compositor if the size matches the display size.
|
||||
metalLayer.drawableSize = [view convertSizeToBacking:view.bounds.size];
|
||||
|
||||
// In its implementation of vkGetPhysicalDeviceSurfaceCapabilitiesKHR, MoltenVK takes into
|
||||
// consideration both the size (in points) of the bounds, and the contentsScale of the
|
||||
// CAMetalLayer from which the Vulkan surface was created.
|
||||
// See also https://github.com/KhronosGroup/MoltenVK/issues/428
|
||||
metalLayer.contentsScale = view.window.backingScaleFactor;
|
||||
|
||||
// This is set to NO by default, but is also important to ensure we can bypass the compositor
|
||||
// in full-screen mode
|
||||
// See "Direct to Display" http://metalkit.org/2017/06/30/introducing-metal-2.html.
|
||||
metalLayer.opaque = YES;
|
||||
|
||||
[view setLayer:metalLayer];
|
||||
return metalLayer;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ float vmax(const vec3 v) {
|
||||
}
|
||||
|
||||
float vmax(const vec4 v) {
|
||||
return max(max(v.x, v.y), max(v.z, v.w));
|
||||
return max(max(v.x, v.y), max(v.y, v.z));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +86,7 @@ float vmin(const vec3 v) {
|
||||
}
|
||||
|
||||
float vmin(const vec4 v) {
|
||||
return min(min(v.x, v.y), min(v.z, v.w));
|
||||
return min(min(v.x, v.y), min(v.y, v.z));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -1453,8 +1453,6 @@ class_<TextureSampler>("TextureSampler")
|
||||
/// Texture ::core class:: 2D image or cubemap that can be sampled by the GPU, possibly mipmapped.
|
||||
class_<Texture>("Texture")
|
||||
.class_function("Builder", (TexBuilder (*)()) [] { return TexBuilder(); })
|
||||
.class_function("isTextureFormatMipmappable", &Texture::isTextureFormatMipmappable)
|
||||
.class_function("validatePixelFormatAndType", &Texture::validatePixelFormatAndType)
|
||||
.function("generateMipmaps", &Texture::generateMipmaps)
|
||||
.function("_setImage", EMBIND_LAMBDA(void, (Texture* self,
|
||||
Engine* engine, uint8_t level, PixelBufferDescriptor pbd), {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.57.2",
|
||||
"version": "1.57.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user