Compare commits
1 Commits
v1.65.2
...
bjd/metal-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15424c85b5 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.65.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.65.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.65.2'
|
||||
pod 'Filament', '~> 1.65.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,9 +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.65.2
|
||||
|
||||
|
||||
## v1.65.1
|
||||
|
||||
- `setFrameScheduledCallback` now works on all backends (frame presentation scheduling is still only
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.65.2
|
||||
VERSION_NAME=1.65.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -78,11 +78,5 @@
|
||||
},
|
||||
"docs_src/README.md": {
|
||||
"dest": "dup/docs.md"
|
||||
},
|
||||
"test/renderdiff/README.md": {
|
||||
"dest": "dup/renderdiff.md",
|
||||
"link_transforms": {
|
||||
"docs/images/": "../images/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
- [Code coverage analysis](./notes/coverage.md)
|
||||
- [Performance analysis](./notes/performance_analysis.md)
|
||||
- [Framegraph](./notes/framegraph.md)
|
||||
- [Tests](./notes/tests.md)
|
||||
- [renderdiff](./dup/renderdiff.md)
|
||||
- [Libraries](./notes/libs.md)
|
||||
- [bluegl](./dup/bluegl.md)
|
||||
- [bluevk](./dup/bluevk.md)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 361 KiB |
@@ -1,4 +0,0 @@
|
||||
# Tests
|
||||
|
||||
Filament has a collection of tests that can be run locally. Many of them are used
|
||||
for in our Continuation Integration flow [on github](https://https://github.com/google/filament/tree/main/.github/workflows).
|
||||
@@ -63,7 +63,6 @@ public:
|
||||
static void copyImage(uint8_t* UTILS_RESTRICT dest,
|
||||
const uint8_t* UTILS_RESTRICT src,
|
||||
size_t srcBytesPerRow, size_t /*srcChannelCount*/,
|
||||
size_t /*dstRowOffset */, size_t /*dstColumnOffset */,
|
||||
size_t dstBytesPerRow, size_t /*dstChannelCount*/,
|
||||
size_t /*width*/, size_t height, bool /*swizzle*/) {
|
||||
if (srcBytesPerRow == dstBytesPerRow) {
|
||||
@@ -80,20 +79,17 @@ public:
|
||||
template<typename dstComponentType, typename srcComponentType>
|
||||
static void reshapeImage(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||
size_t srcBytesPerRow,
|
||||
size_t srcChannelCount,
|
||||
size_t dstRowOffset, size_t dstColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t srcChannelCount, size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t width, size_t height, bool swizzle) {
|
||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
||||
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
||||
assert_invariant(minChannelCount <= 4);
|
||||
UTILS_ASSUME(minChannelCount <= 4);
|
||||
dest += (dstRowOffset * dstBytesPerRow);
|
||||
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
const srcComponentType* in = (const srcComponentType*) src;
|
||||
dstComponentType* out = (dstComponentType*)dest + (dstColumnOffset * dstChannelCount);
|
||||
const srcComponentType* in = (const srcComponentType*)src;
|
||||
dstComponentType* out = (dstComponentType*)dest;
|
||||
for (size_t column = 0; column < width; ++column) {
|
||||
for (size_t channel = 0; channel < minChannelCount; ++channel) {
|
||||
if constexpr (std::is_same_v<dstComponentType, srcComponentType>) {
|
||||
@@ -132,23 +128,17 @@ public:
|
||||
default: return false;
|
||||
}
|
||||
void (*reshaper)(uint8_t* dest, const uint8_t* src, size_t srcBytesPerRow,
|
||||
size_t srcChannelCount,
|
||||
size_t srcRowOffset, size_t srcColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t srcChannelCount, size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t width, size_t height, bool swizzle) = nullptr;
|
||||
constexpr auto UBYTE = PixelDataType::UBYTE;
|
||||
constexpr auto FLOAT = PixelDataType::FLOAT;
|
||||
constexpr auto UINT = PixelDataType::UINT;
|
||||
constexpr auto INT = PixelDataType::INT;
|
||||
constexpr auto HALF = PixelDataType::HALF;
|
||||
constexpr auto UBYTE = PixelDataType::UBYTE, FLOAT = PixelDataType::FLOAT,
|
||||
UINT = PixelDataType::UINT, INT = PixelDataType::INT;
|
||||
switch (dst->type) {
|
||||
case UBYTE:
|
||||
switch (srcType) {
|
||||
case UBYTE:
|
||||
reshaper = reshapeImage<uint8_t, uint8_t>;
|
||||
if (dst->format == PixelDataFormat::RGBA &&
|
||||
dstChannelCount == srcChannelCount && !swizzle && dst->top == 0 &&
|
||||
dst->left == 0) {
|
||||
dstChannelCount == srcChannelCount && !swizzle) {
|
||||
reshaper = copyImage;
|
||||
}
|
||||
break;
|
||||
@@ -185,20 +175,13 @@ public:
|
||||
default: return false;
|
||||
}
|
||||
break;
|
||||
case HALF:
|
||||
switch (srcType) {
|
||||
case HALF: reshaper = copyImage; break;
|
||||
default: return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
uint8_t* dstBytes = (uint8_t*) dst->buffer;
|
||||
const int dstBytesPerRow = PixelBufferDescriptor::computeDataSize(dst->format, dst->type,
|
||||
dst->stride ? dst->stride : width, 1, dst->alignment);
|
||||
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount,
|
||||
dst->top, dst->left, dstBytesPerRow,
|
||||
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount, dstBytesPerRow,
|
||||
dstChannelCount, width, height, swizzle);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ private:
|
||||
Type mType = Type::NONE;
|
||||
|
||||
static PlatformMetal* platform;
|
||||
static std::array<uint64_t, TypeCount> aliveBuffers;
|
||||
static std::array<std::atomic<uint64_t>, TypeCount> aliveBuffers;
|
||||
};
|
||||
|
||||
class MetalBuffer {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
std::array<uint64_t, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
|
||||
std::array<std::atomic<uint64_t>, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
|
||||
PlatformMetal* TrackedMetalBuffer::platform = nullptr;
|
||||
PlatformMetal* ScopedAllocationTimer::platform = nullptr;
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
context->pendingCommandBuffer = [context->commandQueue commandBuffer];
|
||||
// It's safe for this block to capture the context variable. MetalDriver::terminate will ensure
|
||||
// all frames and their completion handlers finish before context is deallocated.
|
||||
const uint64_t thisCommandBufferId = context->pendingCommandBufferId;
|
||||
uint64_t thisCommandBufferId = context->pendingCommandBufferId;
|
||||
[context->pendingCommandBuffer addCompletedHandler:^(id <MTLCommandBuffer> buffer) {
|
||||
context->resourceTracker.clearResources((__bridge void*) buffer);
|
||||
|
||||
@@ -118,11 +118,6 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
context->memorylessLimitsReached = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
|
||||
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
|
||||
<< stringifyMTLCommandBufferError(errorCode) << ").";
|
||||
}
|
||||
}];
|
||||
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
||||
<< "Could not obtain command buffer.";
|
||||
|
||||
@@ -451,38 +451,6 @@ inline MTLTextureSwizzleChannels getSwizzleChannels(TextureSwizzle r, TextureSwi
|
||||
getSwizzle(a));
|
||||
}
|
||||
|
||||
inline const char* stringifyMTLCommandBufferError(MTLCommandBufferError error) {
|
||||
#if !defined(FILAMENT_IOS)
|
||||
if (error == MTLCommandBufferErrorDeviceRemoved) {
|
||||
return "MTLCommandBufferErrorDeviceRemoved";
|
||||
}
|
||||
#endif
|
||||
switch (error) {
|
||||
case MTLCommandBufferErrorNone:
|
||||
return "MTLCommandBufferErrorNone";
|
||||
case MTLCommandBufferErrorInternal:
|
||||
return "MTLCommandBufferErrorInternal";
|
||||
case MTLCommandBufferErrorTimeout:
|
||||
return "MTLCommandBufferErrorTimeout";
|
||||
case MTLCommandBufferErrorPageFault:
|
||||
return "MTLCommandBufferErrorPageFault";
|
||||
case MTLCommandBufferErrorAccessRevoked:
|
||||
return "MTLCommandBufferErrorAccessRevoked";
|
||||
case MTLCommandBufferErrorNotPermitted:
|
||||
return "MTLCommandBufferErrorNotPermitted";
|
||||
case MTLCommandBufferErrorOutOfMemory:
|
||||
return "MTLCommandBufferErrorOutOfMemory";
|
||||
case MTLCommandBufferErrorInvalidResource:
|
||||
return "MTLCommandBufferErrorInvalidResource";
|
||||
case MTLCommandBufferErrorMemoryless:
|
||||
return "MTLCommandBufferErrorMemoryless";
|
||||
case MTLCommandBufferErrorStackOverflow:
|
||||
return "MTLCommandBufferErrorStackOverflow";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
|
||||
@@ -107,11 +107,8 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice
|
||||
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
|
||||
.handleTypes = context.getFenceExportFlags()
|
||||
};
|
||||
fenceCreateInfo.pNext = &exportFenceCreateInfo;
|
||||
|
||||
// Necessary to guard this. Otherwise, swiftshader would throw an error.
|
||||
if (context.getFenceExportFlags()) {
|
||||
fenceCreateInfo.pNext = &exportFenceCreateInfo;
|
||||
}
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
|
||||
}
|
||||
|
||||
|
||||
@@ -269,10 +269,6 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
auto& api = getDriverApi();
|
||||
|
||||
if (api.getFeatureLevel() < FeatureLevel::FEATURE_LEVEL_2) {
|
||||
GTEST_SKIP() << "Skipping test because multi-sampled textures are not supported at Feature level < 2";
|
||||
}
|
||||
|
||||
constexpr int kSrcTexWidth = 256;
|
||||
constexpr int kSrcTexHeight = 256;
|
||||
constexpr int kDstTexWidth = 256;
|
||||
|
||||
@@ -392,6 +392,10 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
|
||||
"render pass. b/422798473");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
@@ -476,6 +480,10 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Crashing when reading pixels without a redundant call to makeCurrent right before the"
|
||||
"render pass. b/422798473");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
api.startCapture();
|
||||
@@ -511,6 +519,8 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, kTexSize);
|
||||
api.update3DImage(texture, /* level*/ 1, 0, 0, 0, kTexSize, kTexSize, 1, std::move(descriptor));
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
@@ -534,12 +544,13 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageMipLevel", 1875922935));
|
||||
}
|
||||
|
||||
EXPECT_IMAGE(defaultRenderTarget,
|
||||
ScreenshotParams(kTexSize, kTexSize, "UpdateImageMipLevel", 1875922935));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <fstream>
|
||||
@@ -82,6 +81,8 @@ public:
|
||||
|
||||
TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Two cases fail, see b/417255941 and b/417255943");
|
||||
// These test scenarios use a known hash of the result pixel buffer to decide pass / fail,
|
||||
// asserting an exact pixel-for-pixel match. So far, rendering on macOS and iPhone have had
|
||||
// deterministic results. Take this test with a grain of salt, however, as other platform / GPU
|
||||
@@ -123,15 +124,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
size_t bufferDimension = getRenderTargetSize();
|
||||
|
||||
size_t getBufferSizeBytes() const {
|
||||
return bufferDimension * bufferDimension * getBytesPerPixels();
|
||||
}
|
||||
|
||||
size_t getBytesPerPixels() const {
|
||||
int bpp;
|
||||
size_t components;
|
||||
int bpp;
|
||||
getPixelInfo(format, type, components, bpp);
|
||||
return bpp;
|
||||
};
|
||||
return bufferDimension * bufferDimension * bpp;
|
||||
}
|
||||
|
||||
// The offset and stride set on the pixel buffer.
|
||||
size_t left = 0, top = 0, alignment = 1;
|
||||
@@ -148,21 +145,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
const size_t width = readRect.width, height = readRect.height;
|
||||
LinearImage image(width, height, 4);
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
|
||||
if (top == 0 && left == 0 && renderTargetBaseSize == readRect.width) {
|
||||
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
|
||||
} else {
|
||||
float* pixelRef = image.getPixelRef();
|
||||
size_t const bpp = getBytesPerPixels();
|
||||
size_t const bpr = bpp * bufferDimension;
|
||||
for (size_t l = left; l < left + width; ++l) {
|
||||
for (size_t t = top; t < top + height; ++t) {
|
||||
for (size_t c = 0; c < 4; ++c) {
|
||||
pixelRef[c + (l - left) * 4 + ((t - top) * 4 * width)] =
|
||||
((uint8_t*)pixelData)[c + l * bpp + t * bpr] / 255.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
|
||||
}
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
|
||||
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));
|
||||
@@ -186,17 +169,14 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
// The normative read pixels test case. Render a white triangle over a blue background and read
|
||||
// the full viewport into a pixel buffer.
|
||||
TestCase const t0(renderTargetBaseSize);
|
||||
TestCase::Rect const subregionRect = {
|
||||
.x = 90,
|
||||
.y = 403,
|
||||
.width = 64,
|
||||
.height = 64,
|
||||
};
|
||||
|
||||
// Check that a subregion of the render target can be read into a pixel buffer.
|
||||
TestCase t2(renderTargetBaseSize);
|
||||
t2.testName = "readPixels_subregion";
|
||||
t2.readRect = subregionRect;
|
||||
t2.readRect.x = 90;
|
||||
t2.readRect.y = 403;
|
||||
t2.readRect.width = 64;
|
||||
t2.readRect.height = 64;
|
||||
t2.bufferDimension = 64;
|
||||
t2.hash = 0xcba7675a;
|
||||
|
||||
@@ -219,17 +199,16 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
|
||||
// Check that readPixels can read a region of the render target into a subregion of a large
|
||||
// buffer.
|
||||
const size_t actualBufferSize = renderTargetBaseSize * 2;
|
||||
constexpr size_t readOffset = 64;;
|
||||
|
||||
assert_invariant(renderTargetBaseSize + readOffset < actualBufferSize);
|
||||
TestCase t5(renderTargetBaseSize);
|
||||
t5.testName = "readPixels_subbuffer";
|
||||
t5.readRect = subregionRect;
|
||||
t5.bufferDimension = actualBufferSize;
|
||||
t5.left = readOffset;
|
||||
t5.top = readOffset;
|
||||
t5.hash = 0x64585D10;
|
||||
t5.readRect.x = 90;
|
||||
t5.readRect.y = 403;
|
||||
t5.readRect.width = 64;
|
||||
t5.readRect.height = 64;
|
||||
t5.bufferDimension = 512;
|
||||
t5.left = 64;
|
||||
t5.top = 64;
|
||||
t5.hash = 0xbaefdb54;
|
||||
|
||||
// Check that readPixels works with integer formats.
|
||||
TestCase t6(renderTargetBaseSize);
|
||||
@@ -374,6 +353,8 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
flushAndWait();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.65.2"
|
||||
spec.version = "1.65.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.65.2/filament-v1.65.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.65.0/filament-v1.65.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
#ifndef TNT_FILABRIDGE_UIBSTRUCTS_H
|
||||
#define TNT_FILABRIDGE_UIBSTRUCTS_H
|
||||
|
||||
#include <math/mat3.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <private/filament/EngineEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
/*
|
||||
@@ -40,8 +38,7 @@ struct alignas(16) vec3 : public std::array<float, 3> {};
|
||||
struct alignas(16) vec4 : public std::array<float, 4> {};
|
||||
|
||||
struct mat33 : public std::array<vec3, 3> {
|
||||
// passing by value informs the compiler that rhs != *this
|
||||
mat33& operator=(math::mat3f const rhs) noexcept {
|
||||
mat33& operator=(math::mat3f const& rhs) noexcept {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
(*this)[i][0] = rhs[i][0];
|
||||
(*this)[i][1] = rhs[i][1];
|
||||
@@ -52,8 +49,7 @@ struct mat33 : public std::array<vec3, 3> {
|
||||
};
|
||||
|
||||
struct mat44 : public std::array<vec4, 4> {
|
||||
// passing by value informs the compiler that rhs != *this
|
||||
mat44& operator=(math::mat4f const rhs) noexcept {
|
||||
mat44& operator=(math::mat4f const& rhs) noexcept {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
(*this)[i][0] = rhs[i][0];
|
||||
(*this)[i][1] = rhs[i][1];
|
||||
|
||||
@@ -146,7 +146,7 @@ protected:
|
||||
Platform mPlatform = Platform::DESKTOP;
|
||||
TargetApi mTargetApi = (TargetApi) 0;
|
||||
Optimization mOptimization = Optimization::PERFORMANCE;
|
||||
Workarounds mWorkarounds = Workarounds::ALL;
|
||||
Workarounds mWorkarounds = Workarounds::NONE;
|
||||
bool mPrintShaders = false;
|
||||
bool mSaveRawVariants = false;
|
||||
bool mGenerateDebugInfo = false;
|
||||
@@ -805,16 +805,17 @@ public:
|
||||
// Returns true if any of the parameter samplers matches the specified type.
|
||||
bool hasSamplerType(SamplerType samplerType) const noexcept;
|
||||
|
||||
static constexpr size_t MAX_PARAMETERS_COUNT = 48;
|
||||
static constexpr size_t MAX_SUBPASS_COUNT = 1;
|
||||
static constexpr size_t MAX_BUFFERS_COUNT = 4;
|
||||
using ParameterList = std::vector<Parameter>;
|
||||
using ParameterList = Parameter[MAX_PARAMETERS_COUNT];
|
||||
using SubpassList = Parameter[MAX_SUBPASS_COUNT];
|
||||
using BufferList = std::vector<std::unique_ptr<filament::BufferInterfaceBlock>>;
|
||||
using ConstantList = std::vector<Constant>;
|
||||
using PushConstantList = std::vector<PushConstant>;
|
||||
|
||||
// returns the number of parameters declared in this material
|
||||
size_t getParameterCount() const noexcept { return mParameters.size(); }
|
||||
uint8_t getParameterCount() const noexcept { return mParameterCount; }
|
||||
|
||||
// returns a list of at least getParameterCount() parameters
|
||||
const ParameterList& getParameters() const noexcept { return mParameters; }
|
||||
@@ -960,6 +961,7 @@ private:
|
||||
bool mShadowMultiplier = false;
|
||||
bool mTransparentShadow = false;
|
||||
|
||||
uint8_t mParameterCount = 0;
|
||||
uint8_t mSubpassCount = 0;
|
||||
|
||||
bool mDoubleSided = false;
|
||||
|
||||
@@ -295,7 +295,8 @@ MaterialBuilder& MaterialBuilder::variable(Variable v,
|
||||
|
||||
MaterialBuilder& MaterialBuilder::parameter(const char* name, size_t size, UniformType type,
|
||||
ParameterPrecision precision) {
|
||||
mParameters.emplace_back(name, type, size, precision );
|
||||
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
|
||||
mParameters[mParameterCount++] = { name, type, size, precision };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -314,8 +315,9 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType sample
|
||||
<< "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
|
||||
" as long as type is not SHADOW";
|
||||
|
||||
mParameters.emplace_back( name, samplerType, format, precision, filterable,
|
||||
multisample, transformName, stages );
|
||||
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
|
||||
mParameters[mParameterCount++] = { name, samplerType, format, precision, filterable,
|
||||
multisample, transformName, stages };
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -639,7 +641,7 @@ MaterialBuilder& MaterialBuilder::shaderDefine(const char* name, const char* val
|
||||
}
|
||||
|
||||
bool MaterialBuilder::hasSamplerType(SamplerType const samplerType) const noexcept {
|
||||
for (size_t i = 0, c = mParameters.size(); i < c; i++) {
|
||||
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
||||
auto const& param = mParameters[i];
|
||||
if (param.isSampler() && param.samplerType == samplerType) {
|
||||
return true;
|
||||
@@ -666,7 +668,7 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
|
||||
BufferInterfaceBlock::Builder ibb;
|
||||
// sampler bindings start at 1, 0 is the ubo
|
||||
uint16_t binding = 1;
|
||||
for (size_t i = 0, c = mParameters.size(); i < c; i++) {
|
||||
for (size_t i = 0, c = mParameterCount; i < c; i++) {
|
||||
auto const& param = mParameters[i];
|
||||
assert_invariant(!param.isSubpass());
|
||||
if (param.isSampler()) {
|
||||
|
||||
@@ -171,7 +171,7 @@ protected:
|
||||
StringReplacementMap mTemplateMap;
|
||||
StringReplacementMap mMaterialParameters;
|
||||
filament::UserVariantFilterMask mVariantFilter = 0;
|
||||
Workarounds mWorkarounds = Workarounds::ALL;
|
||||
Workarounds mWorkarounds = Workarounds::NONE;
|
||||
bool mIncludeEssl1 = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -118,12 +118,12 @@ bool MaterialParser::ignoreLexeme(const MaterialLexeme&, MaterialBuilder&) const
|
||||
}
|
||||
|
||||
static bool reflectParameters(const MaterialBuilder& builder) {
|
||||
size_t const count = builder.getParameterCount();
|
||||
uint8_t const count = builder.getParameterCount();
|
||||
const MaterialBuilder::ParameterList& parameters = builder.getParameters();
|
||||
|
||||
std::cout << "{" << std::endl;
|
||||
std::cout << " \"parameters\": [" << std::endl;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
const MaterialBuilder::Parameter& parameter = parameters[i];
|
||||
std::cout << " {" << std::endl;
|
||||
std::cout << R"( "name": ")" << parameter.name.c_str() << "\"," << std::endl;
|
||||
|
||||
@@ -479,7 +479,7 @@ constexpr TQuaternion<T> TMat33<T>::packTangentFrame(const TMat33<T>& m, size_t
|
||||
template<typename T>
|
||||
constexpr details::TMat33<T> prescaleForNormals(const details::TMat33<T>& m) noexcept {
|
||||
return m * details::TMat33<T>(
|
||||
T(1.0) / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])})));
|
||||
1.0 / std::sqrt(max(float3{length2(m[0]), length2(m[1]), length2(m[2])})));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------
|
||||
|
||||
@@ -13,33 +13,11 @@ description file) and then running gltf_viewer to produce the renderings.
|
||||
In the `test` directory is a list of test descriptions that are specified in json. Please see
|
||||
`sample.json` to parse the structure.
|
||||
|
||||
## Setting up python
|
||||
The `renderdiff` project uses `python` extensively. To install the dependencies for producing
|
||||
renderings, do the following step
|
||||
- Set up a virtual environment (from the root directory)
|
||||
```
|
||||
python3 -m venv venv
|
||||
. ./venv/bin/activate
|
||||
```
|
||||
- Install the rendering dependencies
|
||||
```
|
||||
pip install -r test/renderdiff/src/rendering_requirements.txt
|
||||
```
|
||||
- Install the viewer depdencies
|
||||
```
|
||||
pip install -r test/renderdiff/src/viewer_requirements.txt
|
||||
```
|
||||
- For the commands in the following section, do not exit the virtual environment. Once you've
|
||||
completed all your work, you can exit with
|
||||
```
|
||||
deactivate
|
||||
```
|
||||
|
||||
## Running the test locally
|
||||
- To run the same presbumit as [`test-renderdiff`](presubmit-renderdiff), you can do
|
||||
|
||||
```
|
||||
bash test/renderdiff/local_test.sh
|
||||
bash test/renderdiff/test.sh
|
||||
```
|
||||
|
||||
- This script will generate the renderings based on the current state of your repo.
|
||||
@@ -89,7 +67,7 @@ in the following fashion
|
||||
|
||||
- Copy the new images to their appropriate place in `filament-assets`
|
||||
- Push the `filament-assets` working branch to remote
|
||||
|
||||
|
||||
```
|
||||
git push origin my-pr-branch-golden
|
||||
```
|
||||
@@ -100,39 +78,14 @@ in the following fashion
|
||||
RDIFF_BBRANCH=my-pr-branch-golden
|
||||
```
|
||||
|
||||
### Manually updating the golden repo
|
||||
|
||||
Doing the above has multiple effects:
|
||||
- The presubmit test [`test-renderdiff`][presubmit-renderdiff] will test against the provided
|
||||
branch of the golden repo (i.e. `my-pr-branch-golden`).
|
||||
- If the PR is merged, then there is another workflow that will merge `my-pr-branch-golden` to
|
||||
the `main` branch of the golden repo.
|
||||
|
||||
## Viewing test results
|
||||
We provide a viewer for looking at the result of a test run. The viewer is a webapp that can be used by
|
||||
pointing your browser to a localhost port. If you input the viewer with a PR or a directory, it will
|
||||
parse the test result and show the results and the rendered and/or golden images.
|
||||
|
||||

|
||||
|
||||
To run the viewer of a test output directory that has been generated locally, you would run the
|
||||
following
|
||||
|
||||
```
|
||||
python3 test/renderdiff/src/viewer.py --diff=[test output]
|
||||
```
|
||||
where `[test output]` is a directory containing the `compare_results.json` of the test run.
|
||||
For example, it could be `out/renderdiff/diffs/presubmit` for the standard path to the
|
||||
`presubmit` test output.
|
||||
|
||||
To see the results of a Pull Request initiated test run, you would do the following
|
||||
|
||||
```
|
||||
python3 test/renderdiff/src/viewer.py --pr_number=[PR #] --github_token=[github token]
|
||||
```
|
||||
|
||||
where `[PR #]` is the numeric ID of your pull request, and the `[github token]` is an acess token
|
||||
that you (as a github user) needs to generate ([reference][github_token_ref]).
|
||||
|
||||
[github_token_ref]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
|
||||
[Mesa]: https://docs.mesa3d.org
|
||||
[SwiftShader]: https://github.com/google/swiftshader
|
||||
[presubmit-renderdiff]: https://github.com/google/filament/blob/e85dfe75c86106a05019e13ccdbef67e030af675/.github/workflows/presubmit.yml#L118
|
||||
|
||||
@@ -22,6 +22,7 @@ import concurrent.futures
|
||||
from utils import execute, ArgParseImpl, mkdir_p, mv_f, important_print
|
||||
|
||||
import test_config
|
||||
from golden_manager import GoldenManager
|
||||
from image_diff import same_image
|
||||
from results import RESULT_OK, RESULT_FAILED
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Mako==1.3.10
|
||||
MarkupSafe==3.0.2
|
||||
numpy==2.3.3
|
||||
PyYAML==6.0.2
|
||||
setuptools==80.9.0
|
||||
tifffile==2025.9.9
|
||||
@@ -76,12 +76,9 @@ class TestConfig(RenderingConfig):
|
||||
given_presets = {p.name: p for p in presets}
|
||||
assert all((name in given_presets) for name in apply_presets),\
|
||||
f'used preset {name} which is not in {given_presets}'
|
||||
|
||||
# Note that this needs to applied in order. Models will be overwritten.
|
||||
# Properties will be "added" in order.
|
||||
for preset in apply_presets:
|
||||
rendering.update(given_presets[preset].rendering)
|
||||
preset_models = given_presets[preset].models
|
||||
preset_models += given_presets[preset].models
|
||||
|
||||
assert 'rendering' in data
|
||||
rendering.update(data['rendering'])
|
||||
|
||||
@@ -113,7 +113,7 @@ class ExpandedComparisonResult extends LitElement {
|
||||
#diffCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 22px;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.selector {
|
||||
@@ -151,9 +151,33 @@ class ExpandedComparisonResult extends LitElement {
|
||||
|
||||
_viewer(name, choices, current, viewType) {
|
||||
const url = viewType == 'rendered' ? getCompUrl(current) : getGoldenUrl(current);
|
||||
const onSelect = (ev) => {
|
||||
const testName = ev.target.value;
|
||||
const test = this.tests.find((t) => t.name == testName);
|
||||
if (name == 'left') {
|
||||
this.left = test;
|
||||
this.leftImageLoaded = false;
|
||||
} else {
|
||||
this.right = test;
|
||||
this.rightImageLoaded = false;
|
||||
}
|
||||
this.showDiff = false;
|
||||
};
|
||||
|
||||
const dropdown = () => {
|
||||
if (this.disableDropdowns) {
|
||||
return html`<div class="selector">${current.name} (${viewType})</div>`;
|
||||
}
|
||||
return html`
|
||||
<select class="selector" @change=${onSelect}>
|
||||
${choices.map((c) => html`<option value=${c.name} ?selected=${c.name == current.name}>${c.name}</option>`)}
|
||||
</select>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div style="flex: 1; margin: 0 5px;">
|
||||
<div>${current.name}</div>
|
||||
${dropdown()}
|
||||
<tiff-viewer id="viewer-${name}" class="viewer"
|
||||
name="${current.name}"
|
||||
fileurl="${url}"></tiff-viewer>
|
||||
@@ -381,9 +405,6 @@ class App extends LitElement {
|
||||
selectedTests: {type: Array},
|
||||
comparisonContent: {type: Object},
|
||||
compareMode: {type: Boolean},
|
||||
|
||||
// This is used to cache urls that are not found
|
||||
missingFile: {type: Object},
|
||||
};
|
||||
|
||||
async _init() {
|
||||
@@ -399,27 +420,12 @@ class App extends LitElement {
|
||||
this.selectedTests = [];
|
||||
this.comparisonContent = null;
|
||||
this.compareMode = false;
|
||||
this.missingFile = {
|
||||
[getDiffUrl('undefined')]: true,
|
||||
};
|
||||
this._init();
|
||||
|
||||
this.addEventListener('dialog-closed', () => {
|
||||
this.dialogContent = null;
|
||||
this.comparisonContent = null;
|
||||
});
|
||||
|
||||
this.addEventListener('url-hit', (ev) => {
|
||||
delete this.missingFile[ev.detail.value];
|
||||
this.missingFile = this.missingFile;
|
||||
this.requestUpdate();
|
||||
});
|
||||
|
||||
this.addEventListener('url-miss', (ev) => {
|
||||
this.missingFile[ev.detail.value] = true;
|
||||
this.missingFile = this.missingFile;
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
@@ -464,24 +470,8 @@ class App extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const sortFn = (a, b) => {
|
||||
const aparts = a.name.split('.');
|
||||
const bparts = b.name.split('.');
|
||||
// 0 = test names
|
||||
// 1 = backend
|
||||
// 2 = model
|
||||
for (let i of [0, 2, 1]) {
|
||||
if (aparts[i] < bparts[i]) {
|
||||
return -1;
|
||||
}
|
||||
if (aparts[i] > bparts[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
let passed = this.tests.filter((t) => t.result == 'ok').sort(sortFn);
|
||||
let failed = this.tests.filter((t) => t.result != 'ok').sort(sortFn);
|
||||
let passed = this.tests.filter((t) => t.result == 'ok');
|
||||
let failed = this.tests.filter((t) => t.result != 'ok');
|
||||
const singleTiff = (url) => {
|
||||
return html`<tiff-viewer style="max-width:100px" fileurl="${url}"></tiff-viewer>`;
|
||||
};
|
||||
@@ -512,8 +502,7 @@ class App extends LitElement {
|
||||
[goldenUrl, 'golden'],
|
||||
[compUrl, 'rendered'],
|
||||
[diffUrl, 'diff']
|
||||
].filter((a) => !this.missingFile[a[0]])
|
||||
.map((a) => [singleTiff(a[0]), a[1]])
|
||||
].map((a) => [singleTiff(a[0]), a[1]])
|
||||
.map((a) => wrap(...a));
|
||||
return html`
|
||||
<div class="test-item" @click="${(e)=>this._onClick(t, e)}" >
|
||||
|
||||
@@ -30,20 +30,15 @@ export class TiffViewer extends LitElement {
|
||||
static properties = {
|
||||
fileurl: {type: String, attribute: 'fileurl'},
|
||||
name: {type: String, attribute: 'name'},
|
||||
failedToFetch: {type: Boolean },
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.fileurl = null;
|
||||
this.name = null;
|
||||
this.failedToFetch = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.failedToFetch) {
|
||||
return html``;
|
||||
}
|
||||
return html`<canvas id="tiffCanvas"></canvas>`;
|
||||
}
|
||||
|
||||
@@ -54,33 +49,7 @@ export class TiffViewer extends LitElement {
|
||||
}
|
||||
|
||||
async _updateImage(fileurl) {
|
||||
this.failedToFetch = false;
|
||||
let fileblob = null;
|
||||
try {
|
||||
let res = await fetch(this.fileurl);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Could not find ${this.fileurl}`);
|
||||
}
|
||||
fileblob = await res.arrayBuffer();
|
||||
} catch (error) {
|
||||
this.failedToFetch = true;
|
||||
}
|
||||
if (!fileblob) {
|
||||
const event = new CustomEvent('url-miss', {
|
||||
detail: { value: fileurl },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
return;
|
||||
} else {
|
||||
const event = new CustomEvent('url-hit', {
|
||||
detail: { value: fileurl },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
const fileblob = await ((await fetch(this.fileurl)).arrayBuffer());
|
||||
const canvas = this.shadowRoot.getElementById('tiffCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
blinker==1.9.0
|
||||
certifi==2025.8.3
|
||||
charset-normalizer==3.4.3
|
||||
click==8.2.1
|
||||
Flask==3.1.2
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.6
|
||||
MarkupSafe==3.0.2
|
||||
requests==2.32.5
|
||||
urllib3==2.5.0
|
||||
waitress==3.0.2
|
||||
Werkzeug==3.1.3
|
||||
@@ -1,30 +1,26 @@
|
||||
{
|
||||
"name": "presubmit",
|
||||
"backends": ["opengl", "vulkan", "webgpu"],
|
||||
"model_search_paths": ["third_party/models", "gltf"],
|
||||
"backends": ["opengl", "vulkan"],
|
||||
"model_search_paths": ["third_party/models"],
|
||||
"presets": [
|
||||
{
|
||||
"name": "base",
|
||||
"models": ["Box", "BoxTextured", "Duck", "lucy", "FlightHelmet"],
|
||||
"models": ["lucy", "DamagedHelmet"],
|
||||
"rendering": {
|
||||
"viewer.cameraFocusDistance": 0,
|
||||
"view.postProcessingEnabled": true,
|
||||
"view.dithering": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "helmet_only",
|
||||
"models": ["DamagedHelmet"],
|
||||
"rendering": {}
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"name": "Bloom",
|
||||
"description": "Testing bloom",
|
||||
"apply_presets": ["base", "helmet_only"],
|
||||
"name": "BloomFlare",
|
||||
"description": "Testing bloom and flare",
|
||||
"apply_presets": ["base"],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.65.2",
|
||||
"version": "1.65.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user