Implement grid-based world origin snapping in View (#9917)
* Implement grid-based world origin snapping in View Implement a grid-based world origin snapping system in View to avoid per-frame transform updates in the future. This will allow to improve CPU performance and to enable future caching of acceleration structures like BVH. The feature is protected by the 'view.enable_grid_based_world_origin' feature flag. The grid size can be set manually via View::setGridSize or calculated automatically as 10% of the camera's far plane distance. A 50% hysteresis ratio is applied to prevent rapid origin flipping near grid edges. Exposed the new API to Java and JavaScript bindings and added unit tests in filament_test.cpp. BUGS=[504726278] * Refine grid-based world origin snapping implementation Refine the grid-based world origin snapping in View with several improvements: 1. Support Orthographic Projections: Calculate automatic grid size using projection matrix elements, working for both perspective and ortho without assuming positive near plane. 2. Stable Automatic Grid Size: Only update effective grid size when a position snap occurs. This prevents instability when frustum scale changes smoothly. 3. Immediate Manual Override: Force an immediate snap when user manually changes grid size. Added test cases for ortho and auto-grid size in filament_test.cpp.
This commit is contained in:
@@ -157,6 +157,24 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
|
||||
view->setDynamicResolutionOptions(options);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetGridSize(JNIEnv*, jclass, jlong nativeView, jdouble size) {
|
||||
View* view = (View*) nativeView;
|
||||
view->setGridSize(size);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jdouble JNICALL
|
||||
Java_com_google_android_filament_View_nGetGridSize(JNIEnv*, jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return view->getGridSize();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jdouble JNICALL
|
||||
Java_com_google_android_filament_View_nGetEffectiveGridSize(JNIEnv*, jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return view->getEffectiveGridSize();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
|
||||
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
|
||||
|
||||
@@ -697,6 +697,33 @@ public class View {
|
||||
options.quality.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the grid size for grid-based world origin snapping.
|
||||
*
|
||||
* @param size The size of the grid cell in world units. If set to 0 or negative,
|
||||
* the grid size is automatically calculated based on the camera frustum.
|
||||
*/
|
||||
public void setGridSize(double size) {
|
||||
nSetGridSize(getNativeObject(), size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid size used for grid-based world origin snapping.
|
||||
* @return The grid size in world units. A value of 0 or negative means automatic calculation is enabled.
|
||||
*/
|
||||
public double getGridSize() {
|
||||
return nGetGridSize(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective grid size used for grid-based world origin snapping.
|
||||
* If grid size was set to 0 or negative, this returns the automatically calculated size.
|
||||
* @return The effective grid size in world units.
|
||||
*/
|
||||
public double getEffectiveGridSize() {
|
||||
return nGetEffectiveGridSize(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the dynamic resolution options associated with this view.
|
||||
* @return value set by {@link #setDynamicResolutionOptions}.
|
||||
@@ -1370,6 +1397,9 @@ public class View {
|
||||
private static native void nSetDithering(long nativeView, int dithering);
|
||||
private static native int nGetDithering(long nativeView);
|
||||
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
|
||||
private static native void nSetGridSize(long nativeView, double size);
|
||||
private static native double nGetGridSize(long nativeView);
|
||||
private static native double nGetEffectiveGridSize(long nativeView);
|
||||
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
|
||||
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
|
||||
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
|
||||
|
||||
@@ -590,6 +590,32 @@ public:
|
||||
*/
|
||||
void setDynamicLightingOptions(float zLightNear, float zLightFar) noexcept;
|
||||
|
||||
/**
|
||||
* Sets the grid size for grid-based world origin snapping.
|
||||
*
|
||||
* The world origin used for rendering will snap to a grid of this size.
|
||||
* This avoids recomputing all transforms every frame when the camera moves within a grid cell.
|
||||
*
|
||||
* Hysteresis is applied automatically to avoid rapid snapping near edges.
|
||||
*
|
||||
* @param size The size of the grid cell in world units. If set to 0 or negative,
|
||||
* the grid size is automatically calculated based on the camera frustum.
|
||||
*/
|
||||
void setGridSize(double size) noexcept;
|
||||
|
||||
/**
|
||||
* Returns the grid size used for grid-based world origin snapping.
|
||||
* @return The grid size in world units. A value of 0 or negative means automatic calculation is enabled.
|
||||
*/
|
||||
double getGridSize() const noexcept;
|
||||
|
||||
/**
|
||||
* Returns the effective grid size used for grid-based world origin snapping.
|
||||
* If grid size was set to 0 or negative, this returns the automatically calculated size.
|
||||
* @return The effective grid size in world units.
|
||||
*/
|
||||
double getEffectiveGridSize() const noexcept;
|
||||
|
||||
/*
|
||||
* Set the shadow mapping technique this View uses.
|
||||
*
|
||||
|
||||
@@ -58,6 +58,18 @@ Viewport const& View::getViewport() const noexcept {
|
||||
return downcast(this)->getViewport();
|
||||
}
|
||||
|
||||
void View::setGridSize(double size) noexcept {
|
||||
downcast(this)->setGridSize(size);
|
||||
}
|
||||
|
||||
double View::getGridSize() const noexcept {
|
||||
return downcast(this)->getGridSize();
|
||||
}
|
||||
|
||||
double View::getEffectiveGridSize() const noexcept {
|
||||
return downcast(this)->getEffectiveGridSize();
|
||||
}
|
||||
|
||||
void View::setFrustumCullingEnabled(bool const culling) noexcept {
|
||||
downcast(this)->setFrustumCullingEnabled(culling);
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ FView::FView(FEngine& engine)
|
||||
FgviewerManager* fgviewerManager = engine.debug.fgviewer;
|
||||
if (UTILS_LIKELY(fgviewerManager)) {
|
||||
mFrameGraphViewerViewHandle =
|
||||
fgviewerManager->createView(utils::CString(getName()));
|
||||
fgviewerManager->createView(CString(getName()));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -533,27 +533,132 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc
|
||||
getColorPassDescriptorSet().prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates an automatic grid size based on the camera frustum dimensions.
|
||||
* Handles both perspective and orthographic projections.
|
||||
*
|
||||
* For perspective projections, it uses the width of the frustum at the far plane.
|
||||
* This ensures that the grid size scales with the visible volume and accounts for
|
||||
* field-of-view (zooming in reduces grid size to preserve precision).
|
||||
* For orthographic projections, it uses the absolute width of the frustum.
|
||||
*
|
||||
* camera: The camera to use for the calculation.
|
||||
* Returns the calculated grid size (10% of the computed width, chosen as a
|
||||
* reasonable balance between precision and snapping frequency).
|
||||
*/
|
||||
double FView::calculateAutomaticGridSize(const FCamera* camera) const noexcept {
|
||||
auto const& p = camera->getCullingProjectionMatrix();
|
||||
|
||||
// Base scale is the width of the frustum at Z=1 for perspective,
|
||||
// or the absolute width for orthographic.
|
||||
double baseScale = 2.0 / std::abs(p[0][0]);
|
||||
|
||||
// Detect perspective by checking if P[3][2] is non-zero
|
||||
bool const isPerspective = std::abs(p[3][2]) > 1e-5;
|
||||
|
||||
if (isPerspective) {
|
||||
double const zf = camera->getCullingFar();
|
||||
// Scale at far plane
|
||||
return baseScale * zf * 0.1;
|
||||
} else {
|
||||
// Ortho: baseScale is the full width of the frustum.
|
||||
// Use 10% of the width as grid size.
|
||||
return baseScale * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes a stable grid origin for the camera to improve floating-point precision.
|
||||
* Snapping only occurs when the camera moves beyond the grid boundary plus hysteresis.
|
||||
*
|
||||
* This implementation follows Strategy A: only update the grid size when a position snap occurs.
|
||||
* This prevents instability when the frustum (and thus auto grid size) changes smoothly.
|
||||
* Alternative Strategy B (scale hysteresis) could be used if we want to respond to large scale changes without moving.
|
||||
*
|
||||
* cameraPosition: The current world position of the camera.
|
||||
* currentGridSize: The grid size used in the previous frame (stable).
|
||||
* newGridSize: The new calculated grid size (target).
|
||||
* hysteresisRatio: The hysteresis margin as a ratio of the grid size [0, 1].
|
||||
* forceSnap: Force a snap regardless of threshold (used for manual grid size changes).
|
||||
* Returns the stable grid origin.
|
||||
*/
|
||||
double3 FView::computeGridOrigin(double3 cameraPosition, double currentGridSize, double newGridSize, double hysteresisRatio, bool forceSnap) const noexcept {
|
||||
if (currentGridSize <= 0.0) {
|
||||
return cameraPosition;
|
||||
}
|
||||
|
||||
const double threshold = currentGridSize * (0.5 + hysteresisRatio);
|
||||
const double3 currentOrigin = mGridOrigin;
|
||||
|
||||
// Check threshold per axis (without loop)
|
||||
bool const snapX = std::abs(cameraPosition.x - currentOrigin.x) > threshold;
|
||||
bool const snapY = std::abs(cameraPosition.y - currentOrigin.y) > threshold;
|
||||
bool const snapZ = std::abs(cameraPosition.z - currentOrigin.z) > threshold;
|
||||
|
||||
if (snapX || snapY || snapZ || forceSnap) {
|
||||
// Snap triggered! Use new grid size to compute new origin.
|
||||
double3 const newOrigin = {
|
||||
std::round(cameraPosition.x / newGridSize) * newGridSize,
|
||||
std::round(cameraPosition.y / newGridSize) * newGridSize,
|
||||
std::round(cameraPosition.z / newGridSize) * newGridSize
|
||||
};
|
||||
mGridOrigin = newOrigin;
|
||||
mEffectiveGridSize = newGridSize;
|
||||
}
|
||||
|
||||
return mGridOrigin;
|
||||
}
|
||||
|
||||
CameraInfo FView::computeCameraInfo(FEngine const& engine) const noexcept {
|
||||
FScene const* const scene = getScene();
|
||||
|
||||
/*
|
||||
* We apply a "world origin" to "everything" in order to implement the IBL rotation.
|
||||
* The "world origin" is also used to keep the origin close to the camera position to
|
||||
* The "world origin" is also used to keep the origin close to the camera position (or snapped grid) to
|
||||
* improve fp precision in the shader for large scenes.
|
||||
*/
|
||||
double3 translation;
|
||||
mat3 rotation;
|
||||
double3 translation = 0.0;
|
||||
mat3 rotation{ 1.0f };
|
||||
|
||||
/*
|
||||
* Calculate all camera parameters needed to render this View for this frame.
|
||||
*/
|
||||
FCamera const* const camera = mViewingCamera ? mViewingCamera : mCullingCamera;
|
||||
double3 const cameraPosition = camera->getPosition();
|
||||
|
||||
// Internal policy controlled by feature flag and debug flags
|
||||
if (engine.debug.view.camera_at_origin) {
|
||||
// this moves the camera to the origin, effectively doing all shader computations in
|
||||
// view-space, which improves floating point precision in the shader by staying around
|
||||
// zero, where fp precision is highest. This also ensures that when the camera is placed
|
||||
// very far from the origin, objects are still rendered and lit properly.
|
||||
translation = -camera->getPosition();
|
||||
if (engine.features.view.enable_grid_based_world_origin) {
|
||||
// This moves the camera to a snapped grid origin, improving floating point precision
|
||||
// while avoiding per-frame transform updates for objects as long as the camera
|
||||
// stays within the grid cell (plus hysteresis).
|
||||
|
||||
// Determine the target grid size (either manual or automatic).
|
||||
double newGridSize = mGridSize;
|
||||
if (newGridSize <= 0.0) {
|
||||
newGridSize = calculateAutomaticGridSize(camera);
|
||||
}
|
||||
|
||||
// For the first frame, initialize the effective grid size.
|
||||
double currentGridSize = mEffectiveGridSize;
|
||||
if (currentGridSize <= 0.0) {
|
||||
// First time initialization
|
||||
currentGridSize = newGridSize;
|
||||
mEffectiveGridSize = currentGridSize;
|
||||
}
|
||||
|
||||
// Force snap if user manually changed grid size to a positive value
|
||||
bool const forceSnap = (mGridSize > 0.0 && mGridSize != currentGridSize);
|
||||
|
||||
constexpr double hysteresisRatio = 0.5; // Automatic 50% hysteresis
|
||||
translation = -computeGridOrigin(cameraPosition, currentGridSize, newGridSize, hysteresisRatio, forceSnap);
|
||||
} else {
|
||||
// this moves the camera to the origin, effectively doing all shader computations in
|
||||
// view-space, which improves floating point precision in the shader by staying around
|
||||
// zero, where fp precision is highest. This also ensures that when the camera is placed
|
||||
// very far from the origin, objects are still rendered and lit properly.
|
||||
translation = -cameraPosition;
|
||||
}
|
||||
}
|
||||
|
||||
FIndirectLight const* const ibl = scene->getIndirectLight();
|
||||
@@ -1447,7 +1552,7 @@ void FView::setTemporalAntiAliasingOptions(TemporalAntiAliasingOptions options)
|
||||
|
||||
void FView::setMultiSampleAntiAliasingOptions(MultiSampleAntiAliasingOptions options) noexcept {
|
||||
// MSAA is a post-process effect, and post-processing is disabled at FL0
|
||||
if (mFeatureLevel >= backend::FeatureLevel::FEATURE_LEVEL_1) {
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
||||
options.sampleCount = uint8_t(options.sampleCount < 1u ? 1u : options.sampleCount);
|
||||
mMultiSampleAntiAliasingOptions = options;
|
||||
assert_invariant(!options.enabled || !mRenderTarget || !mRenderTarget->hasSampleableDepth());
|
||||
|
||||
@@ -125,6 +125,10 @@ public:
|
||||
return mViewport;
|
||||
}
|
||||
|
||||
void setGridSize(double size) noexcept { mGridSize = size; }
|
||||
double getGridSize() const noexcept { return mGridSize; }
|
||||
double getEffectiveGridSize() const noexcept { return mEffectiveGridSize; }
|
||||
|
||||
bool getClearTargetColor() const noexcept {
|
||||
// don't clear the color buffer if we have a skybox
|
||||
return !isSkyboxVisible();
|
||||
@@ -541,6 +545,9 @@ private:
|
||||
void prepareVisibleRenderables(utils::JobSystem& js,
|
||||
Frustum const& frustum, FScene::RenderableSoa& renderableData) const noexcept;
|
||||
|
||||
math::double3 computeGridOrigin(math::double3 cameraPosition, double currentGridSize, double newGridSize, double hysteresisRatio, bool forceSnap = false) const noexcept;
|
||||
double calculateAutomaticGridSize(const FCamera* camera) const noexcept;
|
||||
|
||||
void updateUBOs(backend::DriverApi& driver,
|
||||
FScene::RenderableSoa& renderableData,
|
||||
utils::Range<uint32_t> visibleRenderables) noexcept;
|
||||
@@ -583,6 +590,9 @@ private:
|
||||
uint32_t mFroxelConfigurationAge = 0;
|
||||
|
||||
Viewport mViewport;
|
||||
double mGridSize = 0.0;
|
||||
mutable double mEffectiveGridSize = 0.0;
|
||||
mutable math::double3 mGridOrigin{ 0.0 };
|
||||
bool mCulling = true;
|
||||
bool mFrontFaceWindingInverted = false;
|
||||
bool mIsTransparentPickingEnabled = false;
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "details/Camera.h"
|
||||
#include "Froxelizer.h"
|
||||
#include "details/Engine.h"
|
||||
#include "details/View.h"
|
||||
#include "components/RenderableManager.h"
|
||||
#include "components/TransformManager.h"
|
||||
#include "UniformBuffer.h"
|
||||
@@ -803,6 +804,83 @@ TEST(FilamentTest, GoogleLineDirective) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FilamentTest, GridSnapping) {
|
||||
FEngine* engine = downcast(Engine::create(backend::Backend::NOOP));
|
||||
FView* view = downcast(engine->createView());
|
||||
FScene* scene = downcast(engine->createScene());
|
||||
Entity cameraEntity = engine->getEntityManager().create();
|
||||
FCamera* camera = downcast(engine->createCamera(cameraEntity));
|
||||
|
||||
view->setScene(scene);
|
||||
view->setCamera(camera);
|
||||
|
||||
engine->features.view.enable_grid_based_world_origin = true;
|
||||
engine->debug.view.camera_at_origin = true;
|
||||
view->setGridSize(10.0);
|
||||
|
||||
// Test case 1: Camera at (0,0,0)
|
||||
camera->setModelMatrix(mat4::translation(double3{0.0, 0.0, 0.0}));
|
||||
CameraInfo ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, 0.0f, 1e-5f);
|
||||
|
||||
// Test case 2: Camera at (9.9,0,0) - should not snap yet if hysteresis is 50%
|
||||
// Grid size is 10, threshold is 10.
|
||||
camera->setModelMatrix(mat4::translation(double3{9.9, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, 0.0f, 1e-5f);
|
||||
|
||||
// Test case 3: Camera at (10.1,0,0) - should snap to 10
|
||||
camera->setModelMatrix(mat4::translation(double3{10.1, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
// If it snapped to 10, the translation applied to world should be -10
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, -10.0f, 1e-5f);
|
||||
|
||||
// Test case 4: Move back to (1.0,0,0) - should stay at 10 due to hysteresis
|
||||
// Diff to origin (10) is 9.0 < 10.
|
||||
camera->setModelMatrix(mat4::translation(double3{1.0, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, -10.0f, 1e-5f);
|
||||
|
||||
// Test case 5: Move to (-0.1,0,0) - should snap back to 0
|
||||
// Diff to origin (10) is 10.1 > 10.
|
||||
camera->setModelMatrix(mat4::translation(double3{-0.1, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, 0.0f, 1e-5f);
|
||||
|
||||
// Test case 6: Automatic grid size (Perspective)
|
||||
view->setGridSize(0.0); // Enable auto
|
||||
static_cast<Camera*>(camera)->setProjection(90.0, 1.0, 0.1, 100.0, Camera::Fov::VERTICAL); // Set far plane to 100
|
||||
|
||||
// FOV 90, aspect 1.0 -> baseScale = 2.0. Width at far plane = 200.
|
||||
// Auto grid size should be 2.0 * 100 * 0.1 = 20.
|
||||
camera->setModelMatrix(mat4::translation(double3{0.0, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, 0.0f, 1e-5f);
|
||||
|
||||
camera->setModelMatrix(mat4::translation(double3{20.1, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, -20.0f, 1e-5f); // Should snap to 20
|
||||
|
||||
// Test case 7: Ortho automatic grid size
|
||||
view->setGridSize(0.0); // Enable auto
|
||||
static_cast<Camera*>(camera)->setProjection(Camera::Projection::ORTHO, -10.0, 10.0, -10.0, 10.0, -100.0, 100.0);
|
||||
// Width is 20. Auto grid size should be 20 * 0.1 = 2.
|
||||
// Move to -0.1 to trigger snap from previous origin (20) with threshold 20
|
||||
camera->setModelMatrix(mat4::translation(double3{-0.1, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, 0.0f, 1e-5f); // Should snap to 0
|
||||
|
||||
camera->setModelMatrix(mat4::translation(double3{2.1, 0.0, 0.0}));
|
||||
ci = view->computeCameraInfo(*engine);
|
||||
EXPECT_NEAR(ci.worldTransform[3].x, -2.0f, 1e-5f); // Should snap to 2
|
||||
|
||||
engine->destroy(scene);
|
||||
engine->destroy(view);
|
||||
engine->destroyCameraComponent(cameraEntity);
|
||||
engine->getEntityManager().destroy(cameraEntity);
|
||||
Engine::destroy((Engine **)&engine);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@@ -97,6 +97,9 @@ public:
|
||||
bool enable_material_instance_uniform_batching = false;
|
||||
bool enable_fog_as_postprocess = false;
|
||||
} material;
|
||||
struct {
|
||||
bool enable_grid_based_world_origin = false;
|
||||
} view;
|
||||
} features;
|
||||
|
||||
public:
|
||||
|
||||
@@ -57,7 +57,7 @@ std::string_view getFeatureFlagFromEnvironment(char const* const name, std::arra
|
||||
}
|
||||
#endif
|
||||
|
||||
void overrideFeatureDefaults(utils::Slice<FeatureFlagManager::FeatureFlag> const& features) {
|
||||
void overrideFeatureDefaults(Slice<FeatureFlagManager::FeatureFlag> const& features) {
|
||||
std::array<char, 128> storage;
|
||||
UTILS_NOUNROLL
|
||||
for (auto const& feature : features) {
|
||||
@@ -145,6 +145,9 @@ FeatureFlagManager::FeatureFlagManager() : mFeatures{{
|
||||
{ "material.enable_fog_as_postprocess",
|
||||
"Fog is applied as a separate pass for opaque objects.",
|
||||
&features.material.enable_fog_as_postprocess },
|
||||
{ "view.enable_grid_based_world_origin",
|
||||
"Enable grid-based world origin snapping to improve precision and avoid per-frame transform updates.",
|
||||
&features.view.enable_grid_based_world_origin },
|
||||
}} {
|
||||
overrideFeatureDefaults({ mFeatures.data(), mFeatures.size() });
|
||||
}
|
||||
|
||||
@@ -727,6 +727,9 @@ class_<View>("View")
|
||||
.function("getBlendMode", &View::getBlendMode)
|
||||
.function("setViewport", &View::setViewport)
|
||||
.function("getViewport", &View::getViewport)
|
||||
.function("setGridSize", &View::setGridSize)
|
||||
.function("getGridSize", &View::getGridSize)
|
||||
.function("getEffectiveGridSize", &View::getEffectiveGridSize)
|
||||
.function("setVisibleLayers", &View::setVisibleLayers)
|
||||
.function("setPostProcessingEnabled", &View::setPostProcessingEnabled)
|
||||
.function("setDithering", &View::setDithering)
|
||||
|
||||
Reference in New Issue
Block a user