Remove support for Java/desktop builds (#4263)

* Remove support for Java/desktop builds

These builds are never tested nor used on our end. We cannot
guarantee their proper support. It should also be possible
for an app to handle this itself.

* Remove Tungsten since it cannot be compiled anymore
This commit is contained in:
Romain Guy
2021-07-07 17:18:58 -07:00
committed by GitHub
parent 105ae8abb6
commit d6b32e5923
105 changed files with 7 additions and 10344 deletions

View File

@@ -8,10 +8,6 @@ To build Filament, you must first install the following tools:
- clang 7.0 (or more recent)
- [ninja 1.10](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) (or more recent)
To build the Java based components of the project you can optionally install (recommended):
- OpenJDK 1.8 (or more recent)
Additional dependencies may be required for your operating system. Please refer to the appropriate
section below.
@@ -23,10 +19,8 @@ To build Filament for Android you must also install the following:
### Environment variables
Make sure the environment variable `ANDROID_HOME` points to the location of your Android SDK.
By default our build system will attempt to compile the Java bindings. To do so, the environment
variable `JAVA_HOME` should point to the location of your JDK.
To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the
location of your Android SDK.
When building for WebGL, you'll also need to set `EMSDK`. See [WebAssembly](#webassembly).
@@ -65,23 +59,10 @@ To install the libraries and executables in `out/debug/` and `out/release/`, add
You can force a clean build by adding the `-c` flag. The script offers more features described
by executing `build.sh -h`.
### Disabling Java builds
By default our build system will attempt to compile the Java bindings. If you wish to skip this
compilation step simply pass the `-j` flag to `build.sh`:
```
$ ./build.sh -j release
```
If you use CMake directly instead of the build script, pass `-DFILAMENT_ENABLE_JAVA=OFF`
to CMake instead.
### Filament-specific CMake Options
The following CMake options are boolean options specific to Filament:
- `FILAMENT_ENABLE_JAVA`: Compile Java projects: requires a JDK and the JAVA_HOME env var
- `FILAMENT_ENABLE_LTO`: Enable link-time optimizations if supported by the compiler
- `FILAMENT_BUILD_FILAMAT`: Build filamat and JNI buildings
- `FILAMENT_SUPPORTS_OPENGL`: Include the OpenGL backend
@@ -163,14 +144,6 @@ make sure the command line tools are setup by running:
$ xcode-select --install
```
After installing Java 1.8 you must also ensure that your `JAVA_HOME` environment variable is
properly set. If it doesn't already point to the appropriate JDK, you can simply add the following
to your `.profile`:
```
export JAVA_HOME="$(/usr/libexec/java_home)"
```
Then run `cmake` and `ninja` to trigger a build:
```

View File

@@ -11,8 +11,6 @@ project(TNT)
# ==================================================================================================
# Options
# ==================================================================================================
option(FILAMENT_ENABLE_JAVA "Compile Java projects, requires a JDK and the JAVA_HOME env var" ON)
option(FILAMENT_USE_EXTERNAL_GLES3 "Experimental: Compile Filament against OpenGL ES 3" OFF)
option(FILAMENT_USE_SWIFTSHADER "Compile Filament against SwiftShader" OFF)
@@ -655,10 +653,6 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${LIBRARIES}/filamentapp)
add_subdirectory(${LIBRARIES}/imageio)
add_subdirectory(${FILAMENT}/java/filamat)
add_subdirectory(${FILAMENT}/java/filament)
add_subdirectory(${FILAMENT}/java/gltfio)
add_subdirectory(${FILAMENT}/samples)
add_subdirectory(${EXTERNAL}/astcenc/tnt)

View File

@@ -115,7 +115,7 @@ Here are a few screenshots of applications that use Filament in production:
### APIs
- Native C++ API for Android, iOS, Linux, macOS and Windows
- Java/JNI API for Android, Linux, macOS and Windows
- Java/JNI API for Android
- JavaScript API
### Backends
@@ -265,20 +265,6 @@ For complete examples of Linux, macOS and Windows Filament applications, look at
in the `samples/` directory. These samples are all based on `samples/app/` which contains the code
that creates a native window with SDL2 and initializes the Filament engine, renderer and views.
### Java on Linux, macOS and Windows
After building Filament, you can use `filament-java.jar` and its companion `filament-jni` native
library to use Filament in desktop Java applications.
You must always first initialize Filament by calling `Filament.init()`.
You can use Filament either with AWT or Swing, using respectively a `FilamentCanvas` or a
`FilamentPanel`.
Following the steps above (how to use Filament from native code), create an `Engine` and a
`Renderer`, but instead of calling `beginFrame` and `endFrame` on the renderer itself, call
these methods on `FilamentCanvas` or `FilamentPanel`.
### Android
See `android/samples` for examples of how to use Filament on Android.
@@ -330,9 +316,9 @@ and tools.
- `docs`: Documentation
- `math`: Mathematica notebooks used to explore BRDFs, equations, etc.
- `filament`: Filament rendering engine (minimal dependencies)
- `backend`: Rendering backends/drivers (Vulkan, Metal, OpenGL/ES)
- `ide`: Configuration files for IDEs (CLion, etc.)
- `ios`: Sample projects for iOS
- `java`: Java bindings for Filament libraries
- `libs`: Libraries
- `bluegl`: OpenGL bindings for macOS, Linux and Windows
- `bluevk`: Vulkan bindings for macOS, Linux, Windows and Android
@@ -352,6 +338,7 @@ and tools.
- `math`: Math library
- `mathio`: Math types support for output streams
- `utils`: Utility library (threads, memory, data structures, etc.)
- `viewer`: glTF viewer library (requires gltfio)
- `samples`: Sample desktop applications
- `shaders`: Shaders used by `filamat` and `matc`
- `third_party`: External libraries and assets

View File

@@ -5,6 +5,8 @@ A new header is inserted each time a *tag* is created.
## v1.10.8 (currently main branch)
- java: Removed support for Java desktop targets (macOS, Linux, and Windows) [⚠️ **API Change**]
## v1.10.7
- engine: Spot-light position calculation moved to fragment shader.

View File

@@ -1,71 +0,0 @@
/*
* 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 <stdio.h>
#include <jawt.h>
#if defined(__has_include)
#if __has_include(<darwin/jawt_md.h>)
#include <darwin/jawt_md.h>
#else
#include <jawt_md.h>
#endif
#else
#include <darwin/jawt_md.h>
#endif
#include <filament/Engine.h>
#include "JAWTUtils.h"
#import <Cocoa/Cocoa.h>
#pragma clang diagnostic push
#pragma ide diagnostic ignored "NotReleasedValue"
extern "C" {
void *getNativeWindow(JNIEnv *env, jclass klass, jobject surface) {
void *win = nullptr;
JAWT_DrawingSurface *ds = nullptr;
JAWT_DrawingSurfaceInfo *dsi = nullptr;
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
return win;
}
NSObject<JAWT_SurfaceLayers>* jawldsip = (NSObject<JAWT_SurfaceLayers>*)dsi->platformInfo;
// Use jawt_DrawingSurfaceInfo.bounds for frame dimension.
NSView *view = [[NSView alloc] initWithFrame:
NSMakeRect(dsi->bounds.x, dsi->bounds.y, dsi->bounds.width, dsi->bounds.height)];
view.wantsLayer = true;
[jawldsip setLayer:view.layer];
win = (void*) view;
releaseDrawingSurface(ds, dsi);
return win;
}
jlong createNativeSurface(jint width, jint height) {
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
view.wantsLayer = true;
return (jlong) view;
}
void destroyNativeSurface(jlong surface) {
NSView *view = reinterpret_cast<NSView*>(surface);
[view release];
}
}
#pragma clang diagnostic pop

View File

@@ -1,116 +0,0 @@
/*
* 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 "JAWTUtils.h"
#include <vector>
static std::vector<int> jawtVersions = {
0x00010003,
0x00010004,
0x00010007,
0x00010009,
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wuninitialized"
bool acquireDrawingSurface(JNIEnv* env, jobject surface,
JAWT_DrawingSurface** ods, JAWT_DrawingSurfaceInfo** odsi) {
JAWT awt;
JAWT_DrawingSurface* ds = nullptr;
JAWT_DrawingSurfaceInfo* dsi = nullptr;
// Search for a valid AWT
jboolean foundJawt = JNI_FALSE;
for (int jawtVersion : jawtVersions) {
awt.version = jawtVersion;
#if defined(__APPLE__) && defined(__aarch64__)
// FIXME: enable Apple Silicon build. The problem here is that we attempt to link an x86 jdk and that fails
// so JAWT_GetAWT doesn't exist at link time.
#warning "FIXME: JAWT_GetAWT() not supported"
foundJawt = {};
#else
foundJawt = JAWT_GetAWT(env, &awt);
#endif
if (foundJawt == JNI_TRUE) {
#ifndef NDEBUG
printf("Found valid AWT v%08x.\n", jawtVersion);
#endif
break;
} else {
#ifndef NDEBUG
printf("AWT v%08x not present.\n", jawtVersion);
#endif
}
}
#ifndef NDEBUG
fflush(stdout);
#endif
if (foundJawt == JNI_FALSE) {
printf("AWT Not found\n");
fflush(stdout);
return false;
}
// Get the drawing surface
ds = awt.GetDrawingSurface(env, surface);
if (ds == nullptr) {
#ifndef NDEBUG
printf("NULL drawing surface\n");
fflush(stdout);
#endif
return false;
}
// Lock the drawing
jint lock = ds->Lock(ds);
if ((lock & JAWT_LOCK_ERROR) != 0) {
#ifndef NDEBUG
printf("Error locking surface\n");
fflush(stdout);
#endif
awt.FreeDrawingSurface(ds);
return false;
}
// Get the drawing surface info
dsi = ds->GetDrawingSurfaceInfo(ds);
if (dsi == nullptr) {
#ifndef NDEBUG
printf("Error getting surface info\n");
fflush(stdout);
#endif
ds->Unlock(ds);
awt.FreeDrawingSurface(ds);
return false;
}
*odsi = dsi;
*ods = ds;
return true;
}
#pragma clang diagnostic pop
void releaseDrawingSurface(JAWT_DrawingSurface* ds, JAWT_DrawingSurfaceInfo* dsi) {
// Free the drawing surface info
ds->FreeDrawingSurfaceInfo(dsi);
// Unlock the drawing surface
ds->Unlock(ds);
}

View File

@@ -1,23 +0,0 @@
/*
* 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 <jawt.h>
extern "C" {
bool acquireDrawingSurface(JNIEnv* env, jobject surface, JAWT_DrawingSurface** ods,
JAWT_DrawingSurfaceInfo** odsi);
void releaseDrawingSurface(JAWT_DrawingSurface* ds, JAWT_DrawingSurfaceInfo* dsi);
}

View File

@@ -1,90 +0,0 @@
/*
* 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 <stdio.h>
#include <jawt.h>
#include <linux/jawt_md.h>
#include "JAWTUtils.h"
#include<GL/glx.h>
extern "C" {
void *getNativeWindow(JNIEnv* env, jclass, jobject surface) {
void* win = nullptr;
JAWT_DrawingSurface* ds = nullptr;
JAWT_DrawingSurfaceInfo* dsi = nullptr;
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
return win;
}
JAWT_X11DrawingSurfaceInfo* dsi_x11 = (JAWT_X11DrawingSurfaceInfo*) dsi->platformInfo;
win = (void*) dsi_x11->drawable;
releaseDrawingSurface(ds, dsi);
return win;
}
jlong createNativeSurface(jint width, jint height) {
Display* display = XOpenDisplay(nullptr);
int screen = DefaultScreen(display);
Window window = 0;
#ifndef NDEBUG
int major, minor;
glXQueryVersion(display, &major, &minor);
printf("Using GLX v%d.%d\n", major, minor); fflush(stdout);
#endif
static int visualAttributess[] = {
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DOUBLEBUFFER, True,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_DEPTH_SIZE, 24,
None
};
int numConfigs = 0;
GLXFBConfig* configs = glXChooseFBConfig( display, screen, visualAttributess, &numConfigs);
if (numConfigs == 0) {
printf("Unable to find a suitable Framebuffer Configuration.\n"); fflush(stdout);
return 0;
}
int pbufferAttributes[] = {
GLX_PBUFFER_WIDTH, width,
GLX_PBUFFER_HEIGHT, height,
None
};
window = glXCreatePbuffer( display, configs[0], pbufferAttributes );
XFree(configs);
// Make sure pbuffer creation has not been buffered in the event queue (we need it NOW).
XFlush(display);
// Camouflage the pbuffer as a window which are both XID anyway.
return (jlong) window;
}
void destroyNativeSurface(jlong surface) {
const char* displayName = nullptr;
Display* display = XOpenDisplay(displayName);
GLXPbuffer pBuffer = (GLXPbuffer)surface;
glXDestroyPbuffer(display, pBuffer);
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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 <stdio.h>
#include <jawt.h>
#include <win32/jawt_md.h>
#include <windows.h>
#include <utils/unwindows.h>
#include "JAWTUtils.h"
#include <filament/Engine.h>
extern "C" {
void* getNativeWindow(JNIEnv* env, jclass, jobject surface) {
JAWT_DrawingSurface* ds = nullptr;
JAWT_DrawingSurfaceInfo* dsi = nullptr;
if (!acquireDrawingSurface(env, surface, &ds, &dsi)) {
return nullptr;
}
JAWT_Win32DrawingSurfaceInfo* dsi_win32 = (JAWT_Win32DrawingSurfaceInfo*) dsi->platformInfo;
HWND hWnd = dsi_win32->hwnd;
releaseDrawingSurface(ds, dsi);
return (void*) hWnd;
}
jlong createNativeSurface(jint width, jint height) {
// We need to adjust the window size so the "client area" matches width and height requested.
// Otherwise, the window itself will be of dimension width-height but the "client area" and the
// resulting surface will be smaller than requested.
RECT dimension = {0, 0, width, height};
AdjustWindowRect(&dimension, WS_OVERLAPPEDWINDOW, FALSE);
width = dimension.right - dimension.left;
height = dimension.bottom - dimension.top;
HWND window = CreateWindowA("STATIC", "dummy", 0, 0, 0, width, height, NULL, NULL, NULL, NULL);
SetWindowLong(window, GWL_STYLE, 0); //remove all window styles
return (jlong) window;
}
void destroyNativeSurface(jlong surface) {
HWND window = (HWND) surface;
DestroyWindow(window);
}
}

View File

@@ -27,8 +27,6 @@ function print_help {
echo " Always invoke CMake before incremental builds."
echo " -i"
echo " Install build output"
echo " -j"
echo " Do not compile desktop Java projects"
echo " -m"
echo " Compile with make instead of ninja."
echo " -p platform1,platform2,..."
@@ -150,8 +148,6 @@ BUILD_ANDROID_SAMPLES=false
RUN_TESTS=false
FILAMENT_ENABLE_JAVA=ON
INSTALL_COMMAND=
VULKAN_ANDROID_OPTION="-DFILAMENT_SUPPORTS_VULKAN=ON"
@@ -217,7 +213,6 @@ function build_desktop_target {
-DIMPORT_EXECUTABLES_DIR=out \
-DCMAKE_BUILD_TYPE="$1" \
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
-DFILAMENT_ENABLE_JAVA="${FILAMENT_ENABLE_JAVA}" \
${SWIFTSHADER_OPTION} \
${MATDBG_OPTION} \
${deployment_target} \
@@ -694,12 +689,6 @@ function validate_build_command {
exit 1
fi
fi
# Make sure we have Java
local javac_binary=$(command -v javac)
if [[ "${JAVA_HOME}" == "" ]] || [[ ! "${javac_binary}" ]]; then
echo "Warning: JAVA_HOME is not set, skipping Java projects"
FILAMENT_ENABLE_JAVA=OFF
fi
# If building a WebAssembly module, ensure we know where Emscripten lives.
if [[ "${EMSDK}" == "" ]] && [[ "${ISSUE_WEBGL_BUILD}" == "true" ]]; then
echo "Error: EMSDK is not set, exiting"
@@ -774,9 +763,6 @@ while getopts ":hacCfijmp:q:uvslwtdk:" opt; do
i)
INSTALL_COMMAND=install
;;
j)
FILAMENT_ENABLE_JAVA=OFF
;;
m)
BUILD_GENERATOR="Unix Makefiles"
BUILD_COMMAND="make"

View File

@@ -1,81 +0,0 @@
cmake_minimum_required(VERSION 3.19)
project(filament-java)
if (NOT FILAMENT_ENABLE_JAVA)
return()
endif()
find_package(Java)
if (NOT Java_FOUND)
message(WARNING "JDK not found, skipping Java projects")
return()
endif()
find_package(JNI)
if (NOT JNI_FOUND)
message(WARNING "JNI not found, skipping Java projects")
return()
endif()
if (NOT DEFINED ENV{JAVA_HOME})
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
message(WARNING "Skipping Java projects")
return()
endif()
# ==================================================================================================
# JNI bindings
# ==================================================================================================
set(TARGET filamat-jni)
include_directories(${JNI_INCLUDE_DIRS})
set(CXX_STANDARD "-std=c++17")
if (WIN32)
set(CXX_STANDARD "/std:c++17")
endif()
set(FILAMAT_DIR ../../android/filamat-android)
set(JNI_SOURCE_FILES
${FILAMAT_DIR}/src/main/cpp/MaterialBuilder.cpp
)
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
target_link_libraries(${TARGET} filamat)
set(INSTALL_TYPE LIBRARY)
if (WIN32 OR CYGWIN)
set(INSTALL_TYPE RUNTIME)
endif()
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
set(LIB_SUFFIX ".so")
if (APPLE)
set(LIB_SUFFIX ".dylib")
elseif (WIN32)
set(LIB_SUFFIX ".dll")
endif()
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
# ==================================================================================================
# Java APIs
# ==================================================================================================
set(TARGET filamat-java)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
set(FILAMAT_JAVA_DIR ${FILAMAT_DIR}/src/main/java/)
get_filename_component(FILAMAT_JAVA_DIR ${FILAMAT_JAVA_DIR} ABSOLUTE)
set(JAVA_SOURCE_FILES
${FILAMAT_JAVA_DIR}/com/google/android/filament/filamat/MaterialBuilder.java
${FILAMAT_JAVA_DIR}/com/google/android/filament/filamat/MaterialPackage.java
)
add_jar(${TARGET}
SOURCES ${JAVA_SOURCE_FILES}
INCLUDE_JARS ../lib/annotation-1.0.0.jar)
install_jar(${TARGET} lib)

View File

@@ -1,165 +0,0 @@
cmake_minimum_required(VERSION 3.19)
project(filament-java)
if (NOT FILAMENT_ENABLE_JAVA)
return()
endif()
find_package(Java)
if (NOT Java_FOUND)
message(WARNING "JDK not found, skipping Java projects")
return()
endif()
find_package(JNI)
if (NOT JNI_FOUND)
message(WARNING "JNI not found, skipping Java projects")
return()
endif()
if (NOT DEFINED ENV{JAVA_HOME})
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
message(WARNING "Skipping Java projects")
return()
endif()
# ==================================================================================================
# JNI bindings
# ==================================================================================================
set(TARGET filament-jni)
set(CXX_STANDARD "-std=c++17")
if (WIN32)
set(CXX_STANDARD "/std:c++17")
endif()
set(ANDROID_DIR ../../android)
set(FILAMENT_DIR ${ANDROID_DIR}/filament-android)
set(COMMON_DIR ${ANDROID_DIR}/common)
include_directories(${JNI_INCLUDE_DIRS} ${ANDROID_DIR})
set(JNI_SOURCE_FILES
${FILAMENT_DIR}/src/main/cpp/BufferObject.cpp
${FILAMENT_DIR}/src/main/cpp/Camera.cpp
${FILAMENT_DIR}/src/main/cpp/Colors.cpp
${FILAMENT_DIR}/src/main/cpp/ColorGrading.cpp
${FILAMENT_DIR}/src/main/cpp/VertexBuffer.cpp
${FILAMENT_DIR}/src/main/cpp/Engine.cpp
${FILAMENT_DIR}/src/main/cpp/EntityManager.cpp
${FILAMENT_DIR}/src/main/cpp/Fence.cpp
${FILAMENT_DIR}/src/main/cpp/IndexBuffer.cpp
${FILAMENT_DIR}/src/main/cpp/IndirectLight.cpp
${FILAMENT_DIR}/src/main/cpp/LightManager.cpp
${FILAMENT_DIR}/src/main/cpp/Material.cpp
${FILAMENT_DIR}/src/main/cpp/MaterialInstance.cpp
${FILAMENT_DIR}/src/main/cpp/MathUtils.cpp
${FILAMENT_DIR}/src/main/cpp/NativeSurface.cpp
${FILAMENT_DIR}/src/main/cpp/RenderableManager.cpp
${FILAMENT_DIR}/src/main/cpp/Renderer.cpp
${FILAMENT_DIR}/src/main/cpp/RenderTarget.cpp
${FILAMENT_DIR}/src/main/cpp/Scene.cpp
${FILAMENT_DIR}/src/main/cpp/SkyBox.cpp
${FILAMENT_DIR}/src/main/cpp/Stream.cpp
${FILAMENT_DIR}/src/main/cpp/Texture.cpp
${FILAMENT_DIR}/src/main/cpp/TextureSampler.cpp
${FILAMENT_DIR}/src/main/cpp/TransformManager.cpp
${FILAMENT_DIR}/src/main/cpp/View.cpp
# Private utils
${FILAMENT_DIR}/src/main/cpp/Filament.cpp
# Desktop specific
${FILAMENT_DIR}/src/main/cpp/nativewindow/JAWTUtils.cpp
# Common utils
${COMMON_DIR}/CallbackUtils.cpp
${COMMON_DIR}/NioUtils.cpp
)
if (LINUX)
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Linux.cpp)
endif()
if (WIN32)
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Win32.cpp)
endif()
if (APPLE)
list(APPEND JNI_SOURCE_FILES ${FILAMENT_DIR}/src/main/cpp/nativewindow/Darwin.mm)
endif()
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
target_link_libraries(${TARGET} filament ${JAVA_AWT_LIBRARY})
set(INSTALL_TYPE LIBRARY)
if (WIN32 OR CYGWIN)
set(INSTALL_TYPE RUNTIME)
endif()
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
set(LIB_SUFFIX ".so")
if (APPLE)
set(LIB_SUFFIX ".dylib")
elseif (WIN32)
set(LIB_SUFFIX ".dll")
endif()
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
# ==================================================================================================
# Java APIs
# ==================================================================================================
set(TARGET filament-java)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
set(FILAMENT_JAVA_DIR ${FILAMENT_DIR}/src/main/java/)
get_filename_component(FILAMENT_JAVA_DIR ${FILAMENT_JAVA_DIR} ABSOLUTE)
set(JAVA_SOURCE_FILES
${FILAMENT_JAVA_DIR}/com/google/android/filament/Asserts.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Box.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/BufferObject.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Camera.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Colors.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/ColorGrading.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Engine.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Entity.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/EntityInstance.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/EntityManager.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Fence.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Filament.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/IndexBuffer.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/IndirectLight.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/LightManager.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Material.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/MaterialInstance.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/MathUtils.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/NativeSurface.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/NioUtils.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Platform.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/RenderableManager.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Renderer.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/RenderTarget.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Scene.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Skybox.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Stream.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/SwapChain.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Texture.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/TextureSampler.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/TransformManager.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/VertexBuffer.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/View.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/Viewport.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/proguard/UsedByNative.java
${FILAMENT_JAVA_DIR}/com/google/android/filament/proguard/UsedByReflection.java
# Desktop specific
src/java/com/google/android/filament/DesktopPlatform.java
src/java/com/google/android/filament/FilamentCanvas.java
src/java/com/google/android/filament/FilamentPanel.java
src/java/com/google/android/filament/FilamentTarget.java
)
add_jar(${TARGET}
SOURCES ${JAVA_SOURCE_FILES}
INCLUDE_JARS ../lib/annotation-1.0.0.jar)
install_jar(${TARGET} lib)

View File

@@ -1,53 +0,0 @@
/*
* 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.
*/
package com.google.android.filament;
import com.google.android.filament.Platform;
public class DesktopPlatform extends Platform {
public DesktopPlatform() {
}
@Override
void log(String message) {
System.out.println(message);
}
@Override
void warn(String message) {
System.out.println(message);
}
boolean validateStreamSource(Object object) {
return false;
}
@Override
boolean validateSurface(Object object) {
return object instanceof java.awt.Canvas;
}
@Override
boolean validateSharedContext(Object object) {
return false;
}
@Override
long getSharedContextNativeHandle(Object sharedContext) {
return 0;
}
}

View File

@@ -1,72 +0,0 @@
/*
* 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.
*/
package com.google.android.filament;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import com.google.android.filament.Engine;
import com.google.android.filament.SwapChain;
import com.google.android.filament.View;
import com.google.android.filament.Renderer;
import androidx.annotation.NonNull;
public class FilamentCanvas extends Canvas implements FilamentTarget {
private SwapChain mSwapChain;
private boolean mHasPainted;
public FilamentCanvas() {
super();
mHasPainted = false;
}
@Override
public void paint(@NonNull Graphics g) {
// On windows, allowing paint more than once will lock the canvas filament renderer.
// Not allowing it at all will trigger weird behavior where maximizing and returning
// to previous window size prevent SwapBuffer from working properly. super.paint() must
// be allowed exactly once.
if (!mHasPainted) {
super.paint(g);
mHasPainted = true;
}
}
public boolean beginFrame(@NonNull Engine engine, @NonNull Renderer renderer) {
return renderer.beginFrame(getSwapChain(engine), 0L);
}
public void endFrame(Renderer renderer) {
renderer.endFrame();
}
@NonNull
private SwapChain getSwapChain(@NonNull Engine engine) {
if (mSwapChain == null) {
mSwapChain = engine.createSwapChain(this, 0);
}
return mSwapChain;
}
public void destroy(@NonNull Engine engine) {
if (mSwapChain == null) {
return;
}
engine.destroySwapChain(mSwapChain);
mSwapChain = null;
}
}

View File

@@ -1,231 +0,0 @@
/*
* 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.
*/
package com.google.android.filament;
import java.awt.*;
import java.nio.IntBuffer;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JPanel;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.nio.ByteBuffer;
import androidx.annotation.NonNull;
/**
* A Swing friendly component which can be used if an AWT based FilamentCanvas object results in
* issues due to interactions with Swing lightweight pop-ups and tooltips. However keep in mind
* it carries a much higher cost of operation.
*/
public class FilamentPanel extends JPanel implements FilamentTarget {
/**
* A Slot contains a buffer for Filament to write and an Image for Swing to read.
* It is ok for the buffer capacity to be > height * width. The image however MUST
* be exactly of dimension height * width.
*/
class Slot {
IntBuffer buffer;
BufferedImage image;
Slot(int width, int height) {
buffer = ByteBuffer.allocateDirect(4 * width * height).asIntBuffer();
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
}
/**
* SlotPool is a circular buffer which provide "Slot"s where ExecutorService thread can write
* pixels and Swing can read from. Its purpose it to reuse memory and avoid allocation. During
* "normal" operation, SlotPool will generate no memory allocations. During a resize, it will
* have to realloc for each frames if the surface gets bigger. If the new size of the frame is
* smaller, only new BufferedImage are allocated.
*/
private final static int NUM_SLOTS = 4;
class SlotPool {
private ConcurrentLinkedDeque<Slot> queue = new ConcurrentLinkedDeque<>();
public SlotPool() {
for (int i = 0; i < NUM_SLOTS; i++) {
Slot slot = new Slot(1, 1); // Zero sized BufferedImage are forbidden.
queue.add(slot);
}
}
/**
* Returns null if no more slots are available. Otherwise, returns a slot adequate to
* receive and process an image of dimension width * height * RGBA.
*/
public Slot getSlot(int width, int height) {
Slot slot = queue.poll();
// Sorry we are out.
if (slot == null) {
return slot;
}
// The slot can avoid an allocation and reuse the same BufferedImage if dimensions have
// not changed.
if (slot.image.getWidth() != width || slot.image.getHeight() != height) {
slot.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
// The slot can avoid an allocation and reuse the same IntBuffer if it has the capacity.
if (slot.buffer.capacity() < width * height) {
slot.buffer = ByteBuffer.allocateDirect(4 * width * height).asIntBuffer();
} else {
// Reuse the buffer, just reset position
slot.buffer.rewind();
}
return slot;
}
public void putSlot(Slot slot) {
queue.add(slot);
}
}
private final SlotPool mSlotPool = new SlotPool();
private NativeSurface mNativeSurface;
private SwapChain mSwapChain;
private BufferedImage mImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
private final Object mImageLock = new Object();
private int mHeight;
private int mWidth;
private Dimension mBackendDimension = new Dimension(0, 0);
private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor();
// Amount of padding added to surface dimension upon allocating them.
private static final int SURFACE_PADDING = 128;
@Override
public void paint(Graphics g) {
synchronized (mImageLock) {
g.drawImage(mImage, 0, 0, Color.BLACK, null);
}
}
/**
* This must be called on Filament thread.
* Prepare to renders to offscreen native window.
*/
public boolean beginFrame(@NonNull Engine engine, @NonNull Renderer renderer) {
ensureSurface(engine);
return renderer.beginFrame(mSwapChain, 0L);
}
/**
* This must be called on Filament thread.
* Pixels are read from the Gl (located in VRAM) and copied to the JVM (in RAM).
* Colors are converted from ABGR to RGBA. A BufferedImage is created (copy RAM to RAM
* of all pixels). Finally the image is drawn to screen via Graphics.drawImage (one more trip
* from RAM to VRAM).
*/
public void endFrame(@NonNull Renderer renderer) {
// By the time readPixel callback is invoked, the dimension of the Panel may have changed.
// Capture dimension so they match the current GL framebuffer.
int capturedWidth = mWidth;
int capturedHeight = mHeight;
// Get a slot where Filament can write its pixels and where we have an appropriately sized
// BufferedImage.
Slot slot = mSlotPool.getSlot(capturedWidth, capturedHeight);
if (slot == null) {
// It seems Filament is producing data faster than Swing can display it. Skip this frame
// to allow Swing to catch up.
renderer.endFrame();
return;
}
Texture.PixelBufferDescriptor pb = new Texture.PixelBufferDescriptor(slot.buffer,
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, sExecutor, new Runnable() {
@Override
public void run() {
synchronized (mImageLock) {
// Copy pixels read from the GL into the BufferedImage. Then convert from RGBA
// to ARGB because that is what the BufferedImage expects.
int[] data = ((DataBufferInt) slot.image.getRaster().getDataBuffer()).getData();
slot.buffer.get(data, 0, capturedHeight * capturedWidth);
for (int i = 0; i < capturedHeight * capturedWidth; i++) {
int rgba = data[i];
data[i] = (rgba >>> 8) | (rgba << 24);
}
mImage = slot.image;
repaint();
mSlotPool.putSlot(slot);
}
}
});
renderer.readPixels(0, 0, mWidth, mHeight, pb);
renderer.endFrame();
}
private void createSurfaces(@NonNull Engine engine, int width, int height) {
mBackendDimension = new Dimension(width, height);
mNativeSurface = new NativeSurface(width, height);
mSwapChain = engine.createSwapChainFromNativeSurface(mNativeSurface, 0L);
}
private void destroySurfaces(@NonNull Engine engine) {
if (mNativeSurface == null) {
return;
}
// Previous frames using this surface may still be in the pipeline, so we must wait for
// them to finish.
engine.flushAndWait();
mNativeSurface.dispose();
engine.destroySwapChain(mSwapChain);
}
private void ensureSurface(@NonNull Engine engine) {
// Capture the dimension at the time the surface was ensured.
mWidth = getWidth();
mHeight = getHeight();
if (mBackendDimension.width < mWidth || mBackendDimension.height < mHeight) {
destroySurfaces(engine);
// A surface needs to be allocated. Allocate something a little bit bigger than
// necessary in order to avoid reallocating too often if the Panel is resized.
int widthToAllocate = mWidth + SURFACE_PADDING;
int heightToAllocate = mHeight + SURFACE_PADDING;
createSurfaces(engine, widthToAllocate, heightToAllocate);
}
}
/**
* This must be called on Filament thread.
*/
public void destroy(@NonNull Engine engine) {
destroySurfaces(engine);
}
}

View File

@@ -1,25 +0,0 @@
/*
* 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.
*/
package com.google.android.filament;
import androidx.annotation.NonNull;
public interface FilamentTarget {
boolean beginFrame(Engine engine, Renderer renderer);
void endFrame(@NonNull Renderer renderer);
void destroy(@NonNull Engine engine);
}

View File

@@ -1,100 +0,0 @@
cmake_minimum_required(VERSION 3.19)
project(filament-java)
if (NOT FILAMENT_ENABLE_JAVA)
return()
endif()
find_package(Java)
if (NOT Java_FOUND)
message(WARNING "JDK not found, skipping Java projects")
return()
endif()
find_package(JNI)
if (NOT JNI_FOUND)
message(WARNING "JNI not found, skipping Java projects")
return()
endif()
if (NOT DEFINED ENV{JAVA_HOME})
message(WARNING "The JAVA_HOME environment variable must be set to compile Java projects")
message(WARNING "Skipping Java projects")
return()
endif()
# ==================================================================================================
# JNI bindings
# ==================================================================================================
set(TARGET gltfio-jni)
set(CXX_STANDARD "-std=c++17")
if (WIN32)
set(CXX_STANDARD "/std:c++17")
endif()
set(ANDROID_DIR ../../android)
set(GLTFIO_DIR ${ANDROID_DIR}/gltfio-android)
set(COMMON_DIR ${ANDROID_DIR}/common)
include_directories(${JNI_INCLUDE_DIRS} ${ANDROID_DIR})
set(JNI_SOURCE_FILES
${GLTFIO_DIR}/src/main/cpp/Animator.cpp
${GLTFIO_DIR}/src/main/cpp/AssetLoader.cpp
${GLTFIO_DIR}/src/main/cpp/FilamentAsset.cpp
${GLTFIO_DIR}/src/main/cpp/FilamentInstance.cpp
${GLTFIO_DIR}/src/main/cpp/MaterialKey.cpp
${GLTFIO_DIR}/src/main/cpp/MaterialKey.h
${GLTFIO_DIR}/src/main/cpp/UbershaderLoader.cpp
${GLTFIO_DIR}/src/main/cpp/ResourceLoader.cpp
${COMMON_DIR}/CallbackUtils.cpp
${COMMON_DIR}/NioUtils.cpp
)
add_library(${TARGET} SHARED ${JNI_SOURCE_FILES})
target_link_libraries(${TARGET} gltfio_core gltfio_resources)
set(INSTALL_TYPE LIBRARY)
if (WIN32 OR CYGWIN)
set(INSTALL_TYPE RUNTIME)
endif()
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
set(LIB_SUFFIX ".so")
if (APPLE)
set(LIB_SUFFIX ".dylib")
elseif (WIN32)
set(LIB_SUFFIX ".dll")
endif()
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} -x ${CMAKE_INSTALL_PREFIX}/lib/${DIST_DIR}/lib${TARGET}${LIB_SUFFIX})")
# ==================================================================================================
# Java APIs
# ==================================================================================================
set(TARGET gltfio-java)
include(UseJava)
set(CMAKE_JAVA_COMPILE_FLAGS "-source" "1.8" "-target" "1.8")
set(GLTFIO_JAVA_DIR ${GLTFIO_DIR}/src/main/java/)
get_filename_component(GLTFIO_JAVA_DIR ${GLTFIO_JAVA_DIR} ABSOLUTE)
set(JAVA_SOURCE_FILES
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/Animator.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/AssetLoader.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/FilamentAsset.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/FilamentInstance.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/Gltfio.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/MaterialProvider.java
${GLTFIO_JAVA_DIR}/com/google/android/filament/gltfio/ResourceLoader.java
)
add_jar(${TARGET}
SOURCES ${JAVA_SOURCE_FILES}
INCLUDE_JARS
../lib/annotation-1.0.0.jar
filament-java
)
install_jar(${TARGET} lib)

Binary file not shown.

View File

@@ -1,10 +0,0 @@
.gradle
/local.properties
/.idea
.DS_Store
build/
/captures
.externalNativeBuild
out/
*.iml
/core/resources/ibls

View File

@@ -1,92 +0,0 @@
# Tungsten
Tungsten is a node based material editor for the Filament rendering engine.
__Note: Tungsten is still a work-in-progress and is not ready for public at-large consumption yet. Use at your own risk!__
## Prerequisites
Before building Tungsten, you'll need to first build Filament. See Filament's [README](../../README.md) for instructions. Be sure to run `make install` (or the equivalent for your chosen build system) to install Filament binaries to the `dist` folder at the root of Filament. Tungsten relies on `filament-java.jar`, `libfilament-jni`, and tools such as `matc` and `cmgen` in the appropriate directories under `dist`:
```
Filament
|-- dist
| |-- bin
| |-- matc
| |-- cmgen
| |-- lib
| |-- x86_64
| |-- libfilament-jni.*
| filament-java.jar
```
The location of this directory can be changed by updating the `filament_tools_dir` property inside of `gradle.properties`.
You'll also need Java 8 in order to use Tungsten. Tungsten is supported on Windows, Mac, and Linux.
## Getting Started
`cd` into the Tungsten directory. The standalone version of Tungsten can be run directly from [Gradle](https://gradle.org/):
```
$ cd tools/tungsten
$ ./gradlew :standalone:run
```
## Running inside of Android Studio
To run inside of Android Studio, set the `ANDROID_STUDIO` environment variable to point to your install of Android Studio, for example:
```
export ANDROID_STUDIO="/Applications/Android Studio.app" # Mac
set ANDROID_STUDIO=C:\Program Files\Android\Android Studio # Windows
```
Then run
```
$ ./gradlew :plugin:runIde
```
## Building
Tungsten comes in two flavors: a standalone app, and (in the future) an Android Studio plugin. The plugin is still in heavy development and not functional yet.
Both targets can be built using Gradle.
```
$ ./gradlew build
```
To build the standalone only:
```
$ ./gradlew :standalone:build
```
To build the plugin only:
```
$ ./gradlew :plugin:build
```
The Android Studio plugin will be built at `plugin/build/distributions/Tungsten-x.y.z.zip`.
## Running the tests
Tests can be run with
```
$ ./gradlew test
```
## Contributing
Please see Filament's [CONTRIBUTING](../../CONTRIBUTING.md) for details.
## Authors
* **Benjamin Doherty**
## License
Please see Filament's [LICENSE](../../LICENSE) for details.

View File

@@ -1,60 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
}
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
apply from: '../../../android/build/filament-tasks.gradle'
sourceSets {
main {
java {
srcDirs = ['src']
}
resources {
srcDirs = ['resources']
}
}
test {
java {
srcDirs = ['test']
}
}
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.+'
implementation files("../../../dist/lib/filament-java.jar")
implementation 'com.google.code.gson:gson:2.8.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.51"
implementation project(':kotlin-math')
}
generateIbl {
group 'Tungsten'
description 'Generate a preview IBL'
inputFile = file("../../../third_party/environments/venetian_crossroads_2k.hdr")
outputDir = file("resources/ibls")
}
processResources.dependsOn generateIbl
test {
testLogging {
showStandardStreams = true
}
}
repositories {
jcenter()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,44 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.filamesh;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.VertexBuffer;
public class Filamesh {
private final String mName;
private final VertexBuffer mVertexBuffer;
private final IndexBuffer mIndexBuffer;
Filamesh(String name, VertexBuffer vertexBuffer, IndexBuffer indexBuffer) {
mName = name;
mVertexBuffer = vertexBuffer;
mIndexBuffer = indexBuffer;
}
String getName() {
return mName;
}
public VertexBuffer getVertexBuffer() {
return mVertexBuffer;
}
public IndexBuffer getIndexBuffer() {
return mIndexBuffer;
}
}

View File

@@ -1,154 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.filamesh;
import com.google.android.filament.Engine;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.VertexBuffer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
public class FilameshLoader {
public static final String FILAMESH_FILE_IDENTIFIER = "FILAMESH";
private static class Float3 {
float x, y, z;
public Float3(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
}
private static class FilameshHeader {
int versionNumber;
int numberOfParts;
Float3 totalBoundingBoxCenter;
Float3 totalBoundingBoxHalfExtent;
int interleaved;
int positionAttributeOffset;
int positionAttributeStride;
int tangentAttributeOffset;
int tangentAttributeStride;
int colorAttributeOffset;
int colorAttributeStride;
int uv0AttributeOffset;
int uv0AttributeStride;
int uv1AttributeOffset;
int uv1AttributeStride;
int totalVertices;
int verticesSizeInBytes;
int indices16Bit; // 0 if indices are stored as int, 1 if stored as uint16
int totalIndices;
int indicesSizeInBytes;
};
private static boolean readMagicNumber(InputStream in) throws IOException {
final byte[] temp = new byte[FILAMESH_FILE_IDENTIFIER.length()];
in.read(temp);
String tempS = new String(temp, "UTF-8");
return tempS.equals(FILAMESH_FILE_IDENTIFIER);
}
private static FilameshHeader readHeader(InputStream in) throws IOException {
FilameshHeader header = new FilameshHeader();
if (!readMagicNumber(in)) {
System.err.print("Invalid filamesh file.");
System.exit(1);
}
header.versionNumber = IOUtils.readIntLE(in);
header.numberOfParts = IOUtils.readIntLE(in);
header.totalBoundingBoxCenter =
new Float3(IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in));
header.totalBoundingBoxHalfExtent =
new Float3(IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in), IOUtils.readFloat32LE(in));
header.interleaved = IOUtils.readIntLE(in);
header.positionAttributeOffset = IOUtils.readIntLE(in);
header.positionAttributeStride = IOUtils.readIntLE(in);
header.tangentAttributeOffset = IOUtils.readIntLE(in);
header.tangentAttributeStride = IOUtils.readIntLE(in);
header.colorAttributeOffset = IOUtils.readIntLE(in);
header.colorAttributeStride = IOUtils.readIntLE(in);
header.uv0AttributeOffset = IOUtils.readIntLE(in);
header.uv0AttributeStride = IOUtils.readIntLE(in);
header.uv1AttributeOffset = IOUtils.readIntLE(in);
header.uv1AttributeStride = IOUtils.readIntLE(in);
header.totalVertices = IOUtils.readIntLE(in);
header.verticesSizeInBytes = IOUtils.readIntLE(in);
header.indices16Bit = IOUtils.readIntLE(in);
header.totalIndices = IOUtils.readIntLE(in);
header.indicesSizeInBytes = IOUtils.readIntLE(in);
return header;
}
public static Filamesh load(String name, InputStream in, Engine engine) {
try {
FilameshHeader header = readHeader(in);
if (header.numberOfParts > 1) {
System.out.println("Mesh " + name + " has " + header.numberOfParts + " parts.");
System.out.println("Currently, only 1 part supported.");
}
ReadableByteChannel channel = Channels.newChannel(in);
ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(header.verticesSizeInBytes);
vertexBuffer.order(ByteOrder.LITTLE_ENDIAN);
channel.read(vertexBuffer);
vertexBuffer.flip();
ByteBuffer indexBuffer = ByteBuffer.allocateDirect(header.indicesSizeInBytes);
indexBuffer.order(ByteOrder.LITTLE_ENDIAN);
channel.read(indexBuffer);
indexBuffer.flip();
VertexBuffer vb = new VertexBuffer.Builder()
.bufferCount(1)
.vertexCount(header.totalVertices)
.attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.HALF4,
header.positionAttributeOffset, header.positionAttributeStride)
.attribute(VertexBuffer.VertexAttribute.TANGENTS, 0, VertexBuffer.AttributeType.SHORT4,
header.tangentAttributeOffset, header.tangentAttributeStride)
.attribute(VertexBuffer.VertexAttribute.COLOR, 0, VertexBuffer.AttributeType.UBYTE4,
header.colorAttributeOffset, header.colorAttributeStride)
.attribute(VertexBuffer.VertexAttribute.UV0, 0, VertexBuffer.AttributeType.HALF2,
header.uv0AttributeOffset, header.uv0AttributeStride)
.build(engine);
vb.setBufferAt(engine, 0, vertexBuffer);
IndexBuffer ib = new IndexBuffer.Builder()
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
.indexCount(header.totalIndices)
.build(engine);
ib.setBuffer(engine, indexBuffer);
return new Filamesh(name, vb, ib);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Error loading mesh: " + name);
}
return null;
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2015 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.
*/
package com.google.android.filament.filamesh;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
class IOUtils {
private IOUtils() {
}
static void safelyClose(InputStream in) {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
// Ignore
}
}
static int readUnsignedShortBE(InputStream in) throws IOException {
return (in.read() & 0xff) << 8 | (in.read() & 0xff);
}
static int readIntLE(InputStream in) throws IOException {
return (in.read() & 0xff) |
(in.read() & 0xff) << 8 |
(in.read() & 0xff) << 16 |
(in.read() & 0xff) << 24;
}
static float readFloat32LE(InputStream in) throws IOException {
byte[] bytes = new byte[4];
in.read(bytes, 0, 4);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
static long readUnsignedIntLE(InputStream in) throws IOException {
return readIntLE(in) & 0xFFFFFFFFL;
}
}

View File

@@ -1,166 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import com.google.android.filament.Engine;
import com.google.android.filament.FilamentPanel;
import com.google.android.filament.Renderer;
import com.google.android.filament.View;
import com.google.android.filament.tungsten.texture.TextureCache;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public final class Filament {
public static class Viewer {
public Renderer renderer;
public FilamentPanel panel;
public View view;
public void update(long deltaTimeMs) {
}
}
private static class Task {
final Consumer<Engine> runnable;
final Object wait;
public Task(Consumer<Engine> runnable, Object wait) {
this.runnable = runnable;
this.wait = wait;
}
}
private static final Filament FILAMENT_HOLDER = new Filament();
private Thread mFilamentThread;
private Engine mEngine;
private final AtomicBoolean mRunFilament = new AtomicBoolean(true);
private final BlockingQueue<Task> mJobQueue = new LinkedBlockingQueue<>();
private final List<Viewer> mViewList = new LinkedList<>();
private long mPreviousTime = 0;
private Filament() {
}
public static Filament getInstance() {
return FILAMENT_HOLDER;
}
public void start() {
startFilamentThread();
}
public void runOnFilamentThread(Consumer<Engine> c) {
mJobQueue.add(new Task(c, null));
}
public void runImmediatelyOnFilamentThread(Consumer<Engine> c) throws InterruptedException {
Object conditionObject = new Object();
synchronized (conditionObject) {
mJobQueue.add(new Task(c, conditionObject));
conditionObject.wait();
}
}
public void addViewer(Viewer viewer) {
runOnFilamentThread((Engine e) -> mViewList.add(viewer));
}
public void removeViewer(Viewer viewer) {
runOnFilamentThread((Engine e) -> mViewList.remove(viewer));
}
public void shutdown() {
try {
TextureCache.INSTANCE.shutdownAndDestroyTextures();
runImmediatelyOnFilamentThread((Engine e) -> {
// Before shutting down, drain the queue of any remaining jobs. Some jobs that deal
// with cleanup might still remain and we want to ensure these all run.
drainJobQueue();
e.destroy();
mRunFilament.set(false);
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void startFilamentThread() {
mFilamentThread = new Thread(() -> {
mEngine = Engine.create();
while (mRunFilament.get()) {
// Run all of the outstanding jobs
drainJobQueue();
renderViews();
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
mFilamentThread.setPriority(Thread.MAX_PRIORITY);
mFilamentThread.start();
}
public void assertIsFilamentThread() {
assert Thread.currentThread().equals(mFilamentThread);
}
private void drainJobQueue() {
while (!mJobQueue.isEmpty()) {
Task task = mJobQueue.poll();
assert task != null;
task.runnable.accept(mEngine);
if (task.wait != null) {
synchronized (task.wait) {
task.wait.notifyAll();
}
}
}
}
private void renderViews() {
long now = System.currentTimeMillis();
for (Viewer viewer : mViewList) {
// We need to wait until Swing has had a chance to layout the panel before we can
// attempt to render to it. Otherwise it won't have a valid swapchain.
if (viewer.panel.getHeight() == 0 || viewer.panel.getWidth() == 0) {
continue;
}
if (!viewer.panel.beginFrame(mEngine, viewer.renderer)) {
continue;
}
viewer.update(now - mPreviousTime);
viewer.renderer.render(viewer.view);
viewer.panel.endFrame(viewer.renderer);
}
mPreviousTime = System.currentTimeMillis();
}
}

View File

@@ -1,24 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
public class MaterialCompilationException extends Exception {
public MaterialCompilationException(String message, Throwable t) {
super(message, t);
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class MaterialCompiler {
private final String mMatcPath;
public MaterialCompiler(String matcPath) {
mMatcPath = matcPath;
}
public Buffer compile(String source) throws MaterialCompilationException {
try {
File sourceFile = createTempFileForSource(source);
File outputFile = createTempFileForOutput();
executeCompileCommand(sourceFile, outputFile);
return loadFileContent(outputFile.getAbsolutePath());
} catch (IOException | InterruptedException e) {
throw new MaterialCompilationException("Error: " + e.getMessage() + ". Could not " +
"compile material", e);
}
}
private void executeCompileCommand(File sourceFile, File outputFile) throws
IOException, InterruptedException {
Process matcProcess = new ProcessBuilder(mMatcPath, "-o",
outputFile.getAbsolutePath(), sourceFile.getAbsolutePath())
.inheritIO()
.start();
matcProcess.waitFor();
}
private static File createTempFileForOutput() throws IOException {
File compiledOutput = File.createTempFile("filament-material-editor-output", ".bmat");
compiledOutput.deleteOnExit();
return compiledOutput;
}
private static File createTempFileForSource(String source) throws IOException {
File sourceFile = File.createTempFile("filament-material-editor-source", ".mat");
sourceFile.deleteOnExit();
PrintWriter sourceFileWriter = new PrintWriter(sourceFile);
sourceFileWriter.println(source);
sourceFileWriter.close();
return sourceFile;
}
private static Buffer loadFileContent(String path) throws IOException {
try (RandomAccessFile file = new RandomAccessFile(path, "r");
FileChannel fileChannel = file.getChannel()) {
int size = (int) file.length();
ByteBuffer buf = ByteBuffer.allocate(size);
int bytesRead = fileChannel.read(buf);
while (bytesRead != 0) {
bytesRead = fileChannel.read(buf);
}
buf.rewind();
return buf;
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import com.google.android.filament.Engine;
import com.google.android.filament.Material;
import java.nio.Buffer;
import java.util.concurrent.CompletableFuture;
public class MaterialManager {
private final MaterialCompiler mCompiler;
public MaterialManager(String matcPath) {
mCompiler = new MaterialCompiler(matcPath);
}
public CompletableFuture<Material> compileMaterial(String source) {
CompletableFuture<Material> result = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try {
Buffer materialBlob = mCompiler.compile(source);
// The Material load must happen on the Filament thread
Filament.getInstance().runOnFilamentThread((Engine e) -> {
Material m = uploadMaterialBlob(materialBlob, e);
result.complete(m);
});
} catch (MaterialCompilationException e) {
e.printStackTrace();
result.cancel(true);
}
});
return result;
}
private static Material uploadMaterialBlob(Buffer materialBlob, Engine engine) {
Material.Builder materialBuilder = new Material.Builder()
.payload(materialBlob, materialBlob.capacity());
return materialBuilder.build(engine);
}
}

View File

@@ -1,29 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
public final class MathUtils {
private MathUtils() { }
public static float[] createUniformScaleMatrix(float v) {
return new float[]{v, 0, 0, 0,
0, v, 0, 0,
0, 0, v, 0,
0, 0, 0, 1};
}
}

View File

@@ -1,40 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.util.HashMap;
import java.util.Map;
public final class SwingHelper {
private static final RenderingHints HINTS = new RenderingHints(createRenderingHintsMap());
private static Map<RenderingHints.Key, Object> createRenderingHintsMap() {
Map<RenderingHints.Key, Object> map = new HashMap<>();
map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
map.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
return map;
}
private SwingHelper() { }
public static void setRenderingHints(Graphics2D g2d) {
g2d.setRenderingHints(HINTS);
}
}

View File

@@ -1,37 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.Slot
data class CompiledGraph(
// The Filament material definition that is fed into matc
val materialDefinition: String,
// Maps a Node's property to the associated material parameter it controls
val parameterMap: Map<Node.PropertyHandle, Parameter>,
// Maps a Slot to its corresponding Expression
val expressionMap: Map<Slot, Expression>,
/**
* Nodes can change during compilation. This maps from Nodes in the graph pre-compilation to
* equivalent nodes post-compilation.
*/
val oldToNewNodeMap: Map<Node, Node>
)

View File

@@ -1,86 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
// TODO: support other data types- expression assumes a float vector
open class Expression(
open val symbol: String,
open val dimensions: Int
) {
val r get() = conform(1)
val rg get() = conform(2)
val rgb get() = conform(3)
val rgba get() = conform(4)
override fun toString() = symbol
fun conform(components: Int): Expression {
if (dimensions == components) return this
return if (dimensions > components) {
shorten(components)
} else {
lengthen(components)
}
}
open fun lengthen(components: Int): Expression {
val componentsToAdd = components - dimensions
val extraComponents = ", 0.0".repeat(componentsToAdd)
return Expression(symbol = "float$components($symbol$extraComponents)",
dimensions = components)
}
open fun shorten(components: Int): Expression {
val swizzle = when (components) {
1 -> ".r"
2 -> ".rg"
3 -> ".rgb"
else -> ""
}
return Expression(symbol = "$symbol$swizzle", dimensions = components)
}
}
fun floatTypeName(dimensions: Int) = if (dimensions > 1) "float$dimensions" else "float"
private fun createFloatLiteral(dimensions: Int, value: Float): String {
val zeroes = "$value, ".repeat(maxOf(dimensions - 1, 0)) + "$value"
val typeName = floatTypeName(dimensions)
return "$typeName($zeroes)"
}
class Literal(dimensions: Int, private val value: Float = 0.0f) : Expression(
dimensions = dimensions, symbol = createFloatLiteral(dimensions, value)) {
override fun shorten(components: Int) = Literal(dimensions = components, value = value)
override fun lengthen(components: Int) = Literal(dimensions = components, value = value)
}
/**
* Make two expressions the same dimensionality by trimming the higher dimension expression.
*/
fun trim(a: Expression, b: Expression): Pair<Expression, Expression> {
if (a.dimensions == b.dimensions) return Pair(a, b)
val dimensions = Math.min(a.dimensions, b.dimensions)
return if (a.dimensions < b.dimensions) {
Pair(a, b.shorten(dimensions))
} else {
Pair(a.shorten(dimensions), b)
}
}

View File

@@ -1,274 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler;
import com.google.android.filament.tungsten.model.Graph;
import com.google.android.filament.tungsten.model.Node;
import com.google.android.filament.tungsten.model.Slot;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* GraphCompiler takes a a graph of connected NodeModels and generates filament shader code.
*/
public final class GraphCompiler {
public enum CodeSection {
AFTER_PREPARE_MATERIAL,
BEFORE_PREPARE_MATERIAL
}
// Material function source text before the "prepareMaterial" call
private StringBuilder mMaterialFunctionPrologue = new StringBuilder();
// Material function source text after the "prepareMaterial" call
private StringBuilder mMaterialFunctionBodyBuilder = new StringBuilder();
private CodeSection mCurrentCodeSection = CodeSection.AFTER_PREPARE_MATERIAL;
private final List<String> mRequiredAttributes = new ArrayList<>();
private String mShadingModel = "unlit";
private final List<Parameter> mParameters = new ArrayList<>();
private final Map<String, Integer> mParameterNumberMap = new HashMap<>();
private final Map<Node.PropertyHandle, Parameter> mPropertyParameterMap = new HashMap<>();
private final LinkedHashMap<String, String> mGlobalFunctions = new LinkedHashMap<>();
private final Map<String, Integer> mVariableNameMap = new HashMap<>();
private final @NotNull Graph mGraph;
private final @NotNull Node mRootNode;
private final Map<Slot, Expression> mCompiledVariableMap = new HashMap<>();
private final Set<Node> mUncompiledNodes;
private boolean mShouldAppendCode = true;
private final Map<Node, Node> mOldToNewNodeMap = new HashMap<>();
private final List<String> validShadingModels =
Arrays.asList("lit", "unlit", "cloth", "subsurface");
public GraphCompiler(@NotNull Graph graph) {
mGraph = graph;
mRootNode = Objects.requireNonNull(mGraph.getRootNode());
mUncompiledNodes = new HashSet<>(graph.getNodes());
}
@NotNull
public CompiledGraph compileGraph() {
compileNode(mRootNode);
// Compile the rest of the nodes in the graph that aren't necessarily connected to the
// root node. These nodes should not contribute any code to the material definition.
mShouldAppendCode = false;
while (!mUncompiledNodes.isEmpty()) {
Node next = mUncompiledNodes.iterator().next();
compileNode(next);
}
String fragmentSection = GraphFormatter.formatFragmentSection(mGlobalFunctions.values(),
mMaterialFunctionPrologue.toString(), mMaterialFunctionBodyBuilder.toString());
String materialDefinition =
GraphFormatter.formatMaterialSection(mRequiredAttributes, mParameters,
mShadingModel) + fragmentSection;
return new CompiledGraph(materialDefinition, mPropertyParameterMap, mCompiledVariableMap,
mOldToNewNodeMap);
}
/**
* Compile the Node connected to InputSlot and retrieve its Expression entry.
* @return null, if the InputSlot is not connected to any OutputSlot, otherwise an Expression.
*/
@Nullable
public Expression compileAndRetrieveExpression(@NotNull Node.InputSlot slot) {
Node.OutputSlot outputSlot = mGraph.getOutputSlotConnectedToInput(slot);
if (outputSlot == null) {
return null;
}
// Check if we've already compiled
Expression compiledExpression = mCompiledVariableMap.get(outputSlot);
if (compiledExpression != null) {
return compiledExpression;
}
// Compile the connected node
Node connectedNode = mGraph.getNodeForOutputSlot(outputSlot);
if (connectedNode == null) {
throw new RuntimeException("Output slot references node that does not exist in graph.");
}
compileNode(connectedNode);
// Verify that the connected node has set it's output variable
compiledExpression = mCompiledVariableMap.get(outputSlot);
if (compiledExpression == null) {
throw new RuntimeException(
"Output node did not set an output Expression on output slot: " + outputSlot);
}
return compiledExpression;
}
public void setExpressionForSlot(@NotNull Slot slot, @NotNull Expression expression) {
mCompiledVariableMap.put(slot, expression);
}
/**
* Called by a NodeModel subclass to denote that the given vertex attribute is required.
* The attribute will be added to the material source's "requires" section.
*
* @param requirement The name of the vertex attribute, such as "uv0"
*/
public void requireAttribute(String requirement) {
if (!mRequiredAttributes.contains(requirement)) {
mRequiredAttributes.add(requirement);
}
}
public void setShadingModel(String shadingModel) {
if (validShadingModels.contains(shadingModel)) {
mShadingModel = shadingModel;
}
}
/**
* Called by a node's compile function to add a new material parameter.
* The parameter will be added to the material source's "parameters" section.
*
* @param type The type of parameter
* @return A String with the unique name of the parameter that the node should use in code.
*/
@NotNull
public Parameter addParameter(@NotNull String type, @NotNull String name) {
String parameterName = allocateNewParameterName(name);
Parameter parameter = new Parameter(type, parameterName);
mParameters.add(parameter);
return parameter;
}
/**
* Associates a material parameter with a Node's property. After the graph is compiled, this
* mapping between parameters and properties is returned so that adjustments to a property
* can affect the appropriate material parameter.
*/
public void associateParameterWithProperty(@NotNull Parameter parameter,
@NotNull Node.PropertyHandle property) {
mPropertyParameterMap.put(property, parameter);
}
/**
* Called by a NodeModel subclass to allocate a new temporary variable for its use.
* @return The symbol name of the variable.
*/
public String getNewTemporaryVariableName(String name) {
int iterationNumber = mVariableNameMap.getOrDefault(name, 0);
String result = name + iterationNumber;
mVariableNameMap.put(name, ++iterationNumber);
return result;
}
/**
* Called by a NodeModel subclass to add code to the material function's body.
* @param code code to be concatenated to the material function body.
*/
public void addCodeToMaterialFunctionBody(String code) {
if (!mShouldAppendCode) {
return;
}
if (mCurrentCodeSection == CodeSection.AFTER_PREPARE_MATERIAL) {
mMaterialFunctionBodyBuilder.append(code);
} else if (mCurrentCodeSection == CodeSection.BEFORE_PREPARE_MATERIAL) {
mMaterialFunctionPrologue.append(code);
}
}
/**
* Set the state of this GraphCompiler to output code to a specific section of the material
* function body: either before the call to prepareMaterial(), or after.
* The state persists until setCurrentCodeSection is called again.
*/
public void setCurrentCodeSection(CodeSection section) {
mCurrentCodeSection = section;
}
/**
* Convenience method for calling addCodeToMaterialFunctionBody with a format String and
* arguments.
*/
public void addCodeToMaterialFunctionBody(String format, Object... args) {
addCodeToMaterialFunctionBody(String.format(format, args));
}
/**
* Called by a NodeModel to add a global function to the material code.
* @param requestedName The name the Node wishes to use for the function.
* @return The resulting symbol name for the global function. The NodeModel should use this name
* when supplying the function definition and for references to the function in shader code.
*/
public String allocateGlobalFunction(String requestedName, String scope) {
// Mangle the function name
String mangledName = requestedName + "_" + scope;
if (!mGlobalFunctions.containsKey(mangledName)) {
mGlobalFunctions.put(mangledName, "");
}
return mangledName;
}
/**
* Called by a NodeModule to provide a definition for a previously allocated global function.
* @param symbolName The name of the function symbol returned by allocateGlobalFunction.
* @param definition The complete source text of the function, using symbolName.
*/
public void provideFunctionDefinition(String symbolName, String definition) {
mGlobalFunctions.put(symbolName, definition);
}
/**
* Convenience method for providing a function definition that uses a format String and
* arguments.
*/
public void provideFunctionDefinition(String symbolName, String format, Object... args) {
provideFunctionDefinition(symbolName, String.format(format, args));
}
private void compileNode(Node node) {
mUncompiledNodes.remove(node);
Node newNode = node.getCompileFunction().invoke(node, this);
// We've received a new node while compiling, take note of it.
if (newNode != node) {
mOldToNewNodeMap.put(node, newNode);
}
}
/**
* Appends an integer to a parameter name to ensure globally-unique names.
*/
private String allocateNewParameterName(String name) {
Integer parameterNumber =
mParameterNumberMap.computeIfPresent(name, (s, integer) -> integer + 1);
if (parameterNumber == null) {
parameterNumber = 0;
mParameterNumberMap.putIfAbsent(name, parameterNumber);
}
return name + parameterNumber;
}
}

View File

@@ -1,107 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler;
import java.util.Collection;
import java.util.Collections;
final class GraphFormatter {
// Amount of spaces global functions are indented by
private static final int FUNCTION_INDENT_AMOUNT = 4;
// Amount of spaces that the material function body is indented by
private static final int BODY_CODE_INDENT_AMOUNT = 8;
private GraphFormatter() { }
static String formatFragmentSection(Collection<String> globalFunctions,
String materialFunctionBodyPrologue, String materialFunctionBody) {
StringBuilder functions = new StringBuilder();
for (String functionBody : globalFunctions) {
functions.append(indent(functionBody, FUNCTION_INDENT_AMOUNT));
functions.append("\n");
}
return "fragment {\n"
+ functions.toString()
+ " void material(inout MaterialInputs material) {\n"
+ formatMaterialFunctionBody(materialFunctionBodyPrologue ,materialFunctionBody)
+ " }\n"
+ "}";
}
static String formatMaterialSection(Collection<String> attributes,
Collection<Parameter> parameters, String shadingModel) {
StringBuilder builder = new StringBuilder();
for (String attribute : attributes) {
builder.append(indent(attribute, BODY_CODE_INDENT_AMOUNT)).append("\n");
}
String attributeText = builder.toString();
String parameterText = formatParameters(parameters);
return "material {\n"
+ " name : \"\",\n"
+ parameterText
+ " requires : [\n"
+ attributeText
+ " ],\n"
+ " shadingModel : \"" + shadingModel + "\"\n"
+ "}\n"
+ "\n";
}
/**
* Indents each line of a string by amount number of spaces.
*/
static String indent(String s, int amount) {
if (s == null || s.isEmpty()) {
return "";
}
String spaces = String.join("", Collections.nCopies(amount, " "));
String t = s.replaceAll("^", spaces); // indent the first line
return t.replaceAll("\n(?!$)", "\n" + spaces); // indent remaining non-empty lines
}
private static String formatMaterialFunctionBody(String prologue, String epilogue) {
return indent(prologue, BODY_CODE_INDENT_AMOUNT)
+ indent("prepareMaterial(material);\n", BODY_CODE_INDENT_AMOUNT)
+ indent(epilogue, BODY_CODE_INDENT_AMOUNT);
}
private static String formatParameters(Collection<Parameter> parameters) {
if (parameters.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder("parameters : [\n");
int i = 0;
for (Parameter parameter : parameters) {
builder.append(formatParameterSource(parameter));
if (i++ < parameters.size() - 1) {
builder.append(",");
}
builder.append("\n");
}
builder.append("],\n");
return indent(builder.toString(), 4);
}
private static String formatParameterSource(Parameter p) {
return indent("{\n"
+ " type : " + p.getType() + ",\n"
+ " name : " + p.getName() + "\n"
+ "}", 4);
}
}

View File

@@ -1,83 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.NodeId
import com.google.android.filament.tungsten.model.createAddNode
import com.google.android.filament.tungsten.model.createDivideNode
import com.google.android.filament.tungsten.model.createFloat2ConstantNode
import com.google.android.filament.tungsten.model.createFloat3ConstantNode
import com.google.android.filament.tungsten.model.createFloat3ParameterNode
import com.google.android.filament.tungsten.model.createFloatConstantNode
import com.google.android.filament.tungsten.model.createMultiplyNode
import com.google.android.filament.tungsten.model.createPannerNode
import com.google.android.filament.tungsten.model.createShaderNode
import com.google.android.filament.tungsten.model.createSubtractNode
import com.google.android.filament.tungsten.model.createTexCoordNode
import com.google.android.filament.tungsten.model.createTextureSampleNode
import com.google.android.filament.tungsten.model.createTimeNode
import com.google.android.filament.tungsten.model.serialization.INodeFactory
class NodeRegistry : INodeFactory {
/**
* label is a human-readable name used for menus
* typeIdentifier is a string that uniquely identifiers the NodeModel class for serialization
*/
private data class NodeEntry(
val label: String,
val typeIdentifier: String,
val factoryFunction: (id: NodeId) -> Node
)
private val mNodes: List<NodeEntry>
val nodeLabelsForMenu: List<String>
init {
mNodes = listOf(
NodeEntry("Add", "add", createAddNode),
NodeEntry("Subtract", "subtract", createSubtractNode),
NodeEntry("Multiply", "multiply", createMultiplyNode),
NodeEntry("Divide", "divide", createDivideNode),
NodeEntry("Constant float", "floatConstant", createFloatConstantNode),
NodeEntry("Constant float2", "float2Constant", createFloat2ConstantNode),
NodeEntry("Constant float3", "float3Constant", createFloat3ConstantNode),
NodeEntry("Float3 parameter", "float3Parameter", createFloat3ParameterNode),
NodeEntry("Texture sampler", "textureSample", createTextureSampleNode),
NodeEntry("Time", "time", createTimeNode),
NodeEntry("Texture coordinates", "texCoord", createTexCoordNode),
NodeEntry("Texture panner", "panner", createPannerNode),
NodeEntry("Shader", "shader", createShaderNode)
)
nodeLabelsForMenu = mNodes
// The ShaderNode should not be visible in menus
.filter { node -> node.factoryFunction != createShaderNode }
.map { node -> node.label }
}
fun createNodeForLabel(label: String, id: NodeId): Node? {
val entry = mNodes.find { node -> node.label == label }
return entry?.factoryFunction?.invoke(id)
}
override fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node? {
val entry = mNodes.find { node -> node.typeIdentifier == typeIdentifier }
return entry?.factoryFunction?.invoke(id)
}
}

View File

@@ -1,19 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
data class Parameter(val type: String, val name: String)

View File

@@ -1,184 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model
import com.google.android.filament.tungsten.compiler.Expression
import com.google.android.filament.tungsten.compiler.GraphCompiler
import kotlin.reflect.KProperty
data class Connection(
val outputSlot: Node.OutputSlot,
val inputSlot: Node.InputSlot
)
abstract class Slot
typealias NodeId = Int
typealias CompileFunction = (Node, GraphCompiler) -> Node
/**
* An immutable Node in the graph.
* To modify properties of the node, use the automatically-generated .copy function.
*/
data class Node(
// A unique node id to identify this node in a graph
val id: NodeId,
val x: Float = 0.0f,
val y: Float = 0.0f,
val type: String,
// A node's compile function generates code by calling methods on a GraphCompiler. The function
// can return a new node if compilation has changed properties of the node itself.
val compileFunction: CompileFunction = { n, _ -> n },
// Input and output slots of a node are represented by a String name.
val outputSlots: List<String> = emptyList(),
val inputSlots: List<String> = emptyList(),
val properties: List<Property<*>> = emptyList()
) {
data class InputSlot(val nodeId: NodeId, val name: String) : Slot()
data class OutputSlot(val nodeId: NodeId, val name: String) : Slot() {
/**
* This allows some semantic sugar to construct a Connection:
* outputSlot to inputSlot
*/
infix fun to(other: InputSlot): Connection {
return Connection(this, other)
}
}
data class PropertyHandle(val nodeId: NodeId, val name: String)
fun getPropertyHandle(name: String) = PropertyHandle(id, name)
fun getInputSlot(name: String) = InputSlot(id, name)
fun getOutputSlot(name: String) = OutputSlot(id, name)
fun nodeBySettingInputSlots(newInputs: List<String>) =
if (inputSlots === newInputs) this else copy(inputSlots = newInputs)
}
class ConnectionMapper {
operator fun getValue(thisRef: Any?, property: KProperty<*>):
Map<Node.InputSlot, Node.OutputSlot> {
val graph = thisRef as Graph
return graph.connections.associate { c ->
c.inputSlot to c.outputSlot
}
}
}
class NodeMapper {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Map<NodeId, Node> {
val graph = thisRef as Graph
return graph.nodes.associateBy { n -> n.id }
}
}
/**
* An immutable graph.
* To modify, use the generated .copy function or one of the graphBy* convenience functions.
*/
data class Graph(
val nodes: List<Node> = emptyList(),
val rootNodeId: NodeId? = null,
val selection: List<NodeId> = emptyList(),
val connections: List<Connection> = emptyList(),
// Maps from slots to their corresponding Expressions.
val expressionMap: Map<Slot, Expression> = emptyMap()
) {
/**
* connectionMap maps InputSlots to connected OutputSlots and aids graph compilation by
* efficiently answering the question: "is this input connected to any output?"
* It is lazily-computed by ConnectionMapper when needed.
*/
private val connectionMap: Map<Node.InputSlot, Node.OutputSlot> by ConnectionMapper()
/**
* Lazily computes a map from NodeId to Node used for efficient Node lookup.
*/
private val nodeMap: Map<NodeId, Node> by NodeMapper()
/**
* Returns the next available NodeId for this graph.
*/
fun getNewNodeId() = nodes.size
fun getRootNode() = nodeMap[rootNodeId]
fun getNodeWithId(id: NodeId) = nodeMap[id]
fun getNodeProperty(property: Node.PropertyHandle) =
nodeMap[property.nodeId]?.properties?.find { p -> p.name == property.name }
fun getOutputSlotConnectedToInput(slot: Node.InputSlot) = connectionMap[slot]
fun isNodeSelected(node: Node) = selection.contains(node.id)
fun getNodeForOutputSlot(slot: Node.OutputSlot) = nodeMap[slot.nodeId]
fun getSelectedNodes() = nodes.filter { n -> selection.contains(n.id) }
/**
* The following convenience methods return a new copy of the graph with certain attributes
* modified.
*/
fun graphByAddingNode(node: Node) = this.copy(nodes = nodes + node)
fun graphByChangingSelection(selection: List<NodeId>) = this.copy(selection = selection)
fun graphByAddingNodeAtLocation(node: Node, x: Float, y: Float) =
this.copy(nodes = nodes + node.copy(x = x, y = y))
fun graphByReplacingNode(oldNode: Node, newNode: Node) =
graphByReplacingNodes(mapOf(oldNode to newNode))
fun graphByChangingProperty(property: Node.PropertyHandle, value: Property<*>): Graph {
val node = nodeMap[property.nodeId] ?: return this
val newProperties = node.properties.map {
p -> if (p.name == property.name) value else p
}
return graphByReplacingNode(node, node.copy(properties = newProperties))
}
fun graphByMovingNode(node: Node, x: Float, y: Float) =
graphByReplacingNode(node, node.copy(x = x, y = y))
fun graphByReplacingNodes(nodeMap: Map<Node, Node>): Graph {
val newNodes = nodes.map { n -> nodeMap[n] ?: n }
return this.copy(nodes = newNodes)
}
fun graphByFormingConnection(connection: Connection) =
this.copy(connections = connections + connection)
fun graphByRemovingConnection(connection: Connection) =
this.copy(connections = connections - connection)
fun graphBySettingExpressionMap(expressionMap: Map<Slot, Expression>) =
this.copy(expressionMap = expressionMap)
}

View File

@@ -1,136 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model
import com.google.android.filament.tungsten.compiler.Expression
import com.google.android.filament.tungsten.compiler.Literal
import com.google.android.filament.tungsten.compiler.floatTypeName
import com.google.android.filament.tungsten.compiler.trim
data class ResolvedExpressions(
val a: Expression,
val b: Expression,
val resultDimensionality: Int
)
// A function that takes two expressions and prepares them for a math operation.
typealias ExpressionResolver = (Expression?, Expression?) -> ResolvedExpressions
typealias Operation = (Expression, Expression) -> String
/**
* Create a math node compile function with a given operation.
* Assumes two input slots, "a" and "b", and a single output, "out".
*/
private fun createMathCompileFunction(
expressionResolver: ExpressionResolver,
operation: Operation,
temporaryVariable: String
): CompileFunction = { node, compiler ->
val a = compiler.compileAndRetrieveExpression(node.getInputSlot("a"))
val b = compiler.compileAndRetrieveExpression(node.getInputSlot("b"))
val (aExpr, bExpr, resultDimensionality) = expressionResolver(a, b)
compiler.setExpressionForSlot(node.getInputSlot("a"), aExpr)
compiler.setExpressionForSlot(node.getInputSlot("b"), bExpr)
val temp = compiler.getNewTemporaryVariableName(temporaryVariable)
compiler.addCodeToMaterialFunctionBody(
"${floatTypeName(resultDimensionality)} $temp = ${operation(aExpr, bExpr)};\n")
val output = node.getOutputSlot("out")
compiler.setExpressionForSlot(output, Expression(temp, resultDimensionality))
node
}
/**
* Trims two expressions to be the same dimensionality, preferring the one with fewer dimensions.
* If one of the inputs is a single float, neither input is modified, allowing for a scalar
* operations.
*/
internal fun resolveInputExpressions(a: Expression?, b: Expression?): ResolvedExpressions {
val aExpr = a ?: Literal(4)
val bExpr = b ?: Literal(4)
val (trimmedA, trimmedB) = if (aExpr.dimensions > 1 && bExpr.dimensions > 1) {
trim(aExpr, bExpr)
} else {
Pair(aExpr, bExpr)
}
return ResolvedExpressions(trimmedA, trimmedB, maxOf(trimmedA.dimensions, trimmedB.dimensions))
}
private val subtractNodeCompile = createMathCompileFunction(
::resolveInputExpressions,
{ a, b -> "$a - $b" },
"subtract"
)
private val addNodeCompile = createMathCompileFunction(
::resolveInputExpressions,
{ a, b -> "$a + $b" },
"add"
)
private val multiplyNodeCompile = createMathCompileFunction(
::resolveInputExpressions,
{ a, b -> "$a * $b" },
"multiply"
)
private val divideNodeCompile = createMathCompileFunction(
::resolveInputExpressions,
{ a, b -> "$a / $b" },
"divide"
)
val createAddNode = fun(id: NodeId) =
Node(
id = id,
type = "add",
compileFunction = addNodeCompile,
inputSlots = listOf("a", "b"),
outputSlots = listOf("out")
)
val createSubtractNode = fun(id: NodeId) =
Node(
id = id,
type = "subtract",
compileFunction = subtractNodeCompile,
inputSlots = listOf("a", "b"),
outputSlots = listOf("out")
)
val createMultiplyNode = fun(id: NodeId) =
Node(
id = id,
type = "multiply",
compileFunction = multiplyNodeCompile,
inputSlots = listOf("a", "b"),
outputSlots = listOf("out")
)
val createDivideNode = fun(id: NodeId) =
Node(
id = id,
type = "divide",
compileFunction = divideNodeCompile,
inputSlots = listOf("a", "b"),
outputSlots = listOf("out")
)

View File

@@ -1,349 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model
import com.google.android.filament.tungsten.compiler.GraphCompiler
import com.google.android.filament.tungsten.compiler.Expression
import com.google.android.filament.tungsten.compiler.Literal
import com.google.android.filament.tungsten.properties.ColorChooser
import com.google.android.filament.tungsten.properties.FloatSlider
import com.google.android.filament.tungsten.properties.MultipleChoice
import com.google.android.filament.tungsten.properties.TextureFileChooser
private val UNLIT_INPUTS = listOf("baseColor", "emissive")
private val LIT_INPUTS = listOf("normal", "baseColor", "metallic", "roughness", "reflectance",
"clearCoat", "clearCoatRoughness", "anisotropy", "anisotropyDirection", "ambientOcclusion",
"emissive")
private val inputSlotsForShadingModel = { shadingModel: String ->
when (shadingModel) {
"unlit" -> UNLIT_INPUTS
"lit" -> LIT_INPUTS
else -> emptyList()
}
}
private val shaderNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val shadingModel = (node.properties[0].value as StringValue).value
compiler.setShadingModel(shadingModel)
val compileMaterialInput = { name: String, dimensions: Int ->
// The "normal" input is special, and its code must go before the call to prepareMaterial().
if (name == "normal") {
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.BEFORE_PREPARE_MATERIAL)
}
val inputSlot = node.getInputSlot(name)
val connectedExpression = compiler.compileAndRetrieveExpression(inputSlot)
val isConnected = connectedExpression != null
val expression = connectedExpression ?: Literal(dimensions)
// Conform the expression to match the dimensionality of the material input.
val conformedExpression = expression.conform(dimensions)
if (isConnected) {
compiler.addCodeToMaterialFunctionBody("material.$name = $conformedExpression;\n")
}
compiler.setExpressionForSlot(inputSlot, conformedExpression)
compiler.setCurrentCodeSection(GraphCompiler.CodeSection.AFTER_PREPARE_MATERIAL)
}
// Compile inputs common to all shading models.
val compileCommonInputs = {
compileMaterialInput("baseColor", 4)
compileMaterialInput("emissive", 4)
}
// Compile inputs unique to the "lit" shading model.
val compileLitInputs = {
compileMaterialInput("metallic", 1)
compileMaterialInput("roughness", 1)
compileMaterialInput("reflectance", 1)
compileMaterialInput("clearCoat", 1)
compileMaterialInput("clearCoatRoughness", 1)
compileMaterialInput("anisotropy", 1)
compileMaterialInput("anisotropyDirection", 3)
compileMaterialInput("ambientOcclusion", 1)
}
if (shadingModel == "lit") {
// "normal" must be compiled first. If it has any dependencies that are connected to other
// inputs, we need to ensure their code comes before prepareMaterial()
compileMaterialInput("normal", 3)
}
compileCommonInputs()
when (shadingModel) {
"lit" -> compileLitInputs()
}
// If the input slots have changed, return a new node.
return node.nodeBySettingInputSlots(inputSlotsForShadingModel(shadingModel))
}
private val constantFloat3NodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val outputSlot = node.getOutputSlot("result")
val x = (node.properties[0].value as FloatValue)
val y = (node.properties[1].value as FloatValue)
val z = (node.properties[2].value as FloatValue)
val outputVariable = compiler.getNewTemporaryVariableName("float3Constant")
compiler.addCodeToMaterialFunctionBody(
"float3 $outputVariable = float3(${x.v}, ${y.v}, ${z.v});\n")
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 3))
return node
}
private val float3ParameterNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val parameter = compiler.addParameter("float3", "float3Parameter")
compiler.associateParameterWithProperty(parameter, node.getPropertyHandle("value"))
compiler.setExpressionForSlot(node.getOutputSlot("result"),
Expression("materialParams.${parameter.name}", 3))
return node
}
private val constantFloatNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val value = (node.properties[0].value as FloatValue)
val outputVariable = compiler.getNewTemporaryVariableName("floatConstant")
compiler.addCodeToMaterialFunctionBody("float $outputVariable = ${value.v};\n")
val outputSlot = node.getOutputSlot("out")
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 1))
return node
}
private val constantFloat2NodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val outputSlot = node.getOutputSlot("result")
val x = (node.properties[0].value as FloatValue)
val y = (node.properties[1].value as FloatValue)
val outputVariable = compiler.getNewTemporaryVariableName("float2Constant")
compiler.addCodeToMaterialFunctionBody(
"float2 $outputVariable = float2(${x.v}, ${y.v});\n")
compiler.setExpressionForSlot(outputSlot, Expression(outputVariable, 2))
return node
}
/**
* Process UV inputs, defaulting to getUV0() if nothing is connected.
*/
private fun resolveUvInput(input: Expression?, compiler: GraphCompiler): Expression {
return if (input != null) {
input.rg
} else {
compiler.requireAttribute("uv0")
Expression("getUV0()", 2)
}
}
private val textureSampleCompile = fun(node: Node, compiler: GraphCompiler): Node {
val parameter = compiler.addParameter("sampler2d", "texture")
compiler.associateParameterWithProperty(parameter, node.getPropertyHandle("textureSource"))
// If nothing is connected to the UV input, default to getUV0()
val uvInput = compiler.compileAndRetrieveExpression(node.getInputSlot("uv"))
val uvs = resolveUvInput(uvInput, compiler)
compiler.setExpressionForSlot(node.getInputSlot("uv"), uvs)
val outputVariable = compiler.getNewTemporaryVariableName("textureSample")
compiler.addCodeToMaterialFunctionBody(
"float4 $outputVariable = texture(materialParams_${parameter.name}, $uvs);\n", 4)
compiler.setExpressionForSlot(node.getOutputSlot("out"),
Expression(outputVariable, 4))
return node
}
private val timeNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression("getTime()", 1))
return node
}
private val texCoordNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
compiler.requireAttribute("uv0")
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression("getUV0()", 2))
return node
}
private val pannerNodeCompile = fun(node: Node, compiler: GraphCompiler): Node {
val panFunction = compiler.allocateGlobalFunction("pan", "panner")
compiler.provideFunctionDefinition(panFunction, """
float2 $panFunction(float2 coords, float2 speed) {
float2 invSpeed = 1.0 / speed;
float2 offset = mod(float2(getTime(), getTime()), invSpeed) / invSpeed;
float2 panned = coords + offset;
return float2(fract(panned.x), fract(panned.y));
}
""".trimIndent())
val uvInput = compiler.compileAndRetrieveExpression(node.getInputSlot("uv"))
val uvs = resolveUvInput(uvInput, compiler)
val speedSlot = node.getInputSlot("speed")
val speedInput = compiler.compileAndRetrieveExpression(node.getInputSlot("speed"))
?: Literal(2, 1.0f)
compiler.setExpressionForSlot(node.getInputSlot("uv"), uvs)
compiler.setExpressionForSlot(speedSlot, speedInput)
val outputExpression = "$panFunction(${uvs}, $speedInput)"
compiler.setExpressionForSlot(node.getOutputSlot("out"), Expression(outputExpression, 2))
return node
}
val createTextureSampleNode = fun(id: NodeId) =
Node(
id = id,
type = "textureSample",
compileFunction = textureSampleCompile,
inputSlots = listOf("uv"),
outputSlots = listOf("out"),
properties = listOf(Property(
name = "textureSource",
value = TextureFile(),
type = PropertyType.MATERIAL_PARAMETER,
editorFactory = ::TextureFileChooser
))
)
val createFloat3ConstantNode = fun(id: NodeId): Node {
return Node(
id = id,
type = "float3Constant",
compileFunction = constantFloat3NodeCompile,
outputSlots = listOf("result"),
properties = listOf(
Property(
name = "x",
value = FloatValue(),
editorFactory = ::FloatSlider
),
Property(
name = "y",
value = FloatValue(),
editorFactory = ::FloatSlider
),
Property(
name = "z",
value = FloatValue(),
editorFactory = ::FloatSlider
)
)
)
}
val createFloat3ParameterNode = fun(id: NodeId): Node {
return Node(
id = id,
type = "float3Parameter",
compileFunction = float3ParameterNodeCompile,
outputSlots = listOf("result"),
properties = listOf(Property(
name = "value",
value = Float3(),
type = PropertyType.MATERIAL_PARAMETER,
editorFactory = ::ColorChooser))
)
}
val createFloatConstantNode = fun(id: NodeId) =
Node(
id = id,
type = "floatConstant",
compileFunction = constantFloatNodeCompile,
outputSlots = listOf("out"),
properties = listOf(Property(
name = "value",
value = FloatValue(),
editorFactory = ::FloatSlider
))
)
val createFloat2ConstantNode = fun(id: NodeId): Node {
return Node(
id = id,
type = "float2Constant",
compileFunction = constantFloat2NodeCompile,
outputSlots = listOf("result"),
properties = listOf(
Property(
name = "x",
value = FloatValue(),
editorFactory = ::FloatSlider
),
Property(
name = "y",
value = FloatValue(),
editorFactory = ::FloatSlider
)
)
)
}
val createTimeNode = fun(id: NodeId): Node =
Node(
id = id,
type = "time",
compileFunction = timeNodeCompile,
outputSlots = listOf("out")
)
val createTexCoordNode = fun(id: NodeId): Node =
Node(
id = id,
type = "texCoord",
compileFunction = texCoordNodeCompile,
outputSlots = listOf("out")
)
val createPannerNode = fun(id: NodeId): Node =
Node(
id = id,
type = "panner",
compileFunction = pannerNodeCompile,
inputSlots = listOf("uv", "speed"),
outputSlots = listOf("out")
)
val createShaderNode = fun(id: NodeId): Node {
return Node(
id = id,
type = "shader",
compileFunction = shaderNodeCompile,
inputSlots = inputSlotsForShadingModel("unlit"),
properties = listOf(Property(
name = "materialModel",
value = StringValue("unlit"),
editorFactory = { MultipleChoice(it, listOf("lit", "unlit")) })))
}
object GraphInitializer {
fun getInitialGraphState(): Graph {
val shaderNode = createShaderNode(0)
return Graph(nodes = listOf(shaderNode), rootNodeId = 0)
}
}

View File

@@ -1,154 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model
import com.google.android.filament.MaterialInstance
import com.google.android.filament.TextureSampler
import com.google.android.filament.tungsten.Filament
import com.google.android.filament.tungsten.properties.PropertyEditor
import com.google.android.filament.tungsten.texture.TextureCache
import com.google.android.filament.tungsten.texture.TextureUtils
import java.io.File
sealed class PropertyValue {
open fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) { }
/**
* Serialize this value into a Kotlin List, Map, String, or Number
*/
abstract fun serialize(): Any?
/**
* Deserialize into a new PropertyValue
*/
abstract fun deserialize(value: Any): PropertyValue
}
data class FloatValue(val v: Float = 0.0f) : PropertyValue() {
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
materialInstance.setParameter(name, v)
}
override fun serialize() = v
override fun deserialize(value: Any): PropertyValue {
if (value !is Number) return this
return FloatValue(value.toFloat())
}
}
data class Float3(val x: Float = 0.0f, val y: Float = 0.0f, val z: Float = 0.0f) : PropertyValue() {
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
materialInstance.setParameter(name, x, y, z)
}
override fun serialize() = listOf(x, y, z)
override fun deserialize(value: Any): Float3 {
if (value !is List<*> || value.size < 3) return this
val (x, y, z) = value
if (x !is Number || y !is Number || z !is Number) return this
return Float3(x.toFloat(), y.toFloat(), z.toFloat())
}
}
data class StringValue(val value: String) : PropertyValue() {
override fun serialize() = value
override fun deserialize(value: Any): StringValue {
if (value !is String) return this
return StringValue(value)
}
}
data class TextureFile(
val file: File? = null,
val colorSpace: TextureUtils.ColorSpaceStrategy =
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
) : PropertyValue() {
override fun serialize(): Any? {
file ?: return null
return mapOf(
"path" to file.canonicalPath,
"colorSpace" to colorSpace.name
)
}
private val textureFuture = if (file != null) {
TextureCache.getTextureForFile(file, colorSpace)
} else {
TextureCache.getDefaultTexture()
}
override fun deserialize(value: Any): PropertyValue {
if (value !is Map<*, *>) return this
val path = value["path"] as? String
val file = if (path != null) {
File(path)
} else {
null
}
val colorSpaceString = value["colorSpace"] as? String
val colorSpace = if (colorSpaceString != null) {
TextureUtils.ColorSpaceStrategy.valueOf(colorSpaceString)
} else {
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
}
return TextureFile(file, colorSpace)
}
override fun applyToMaterialInstance(materialInstance: MaterialInstance, name: String) {
textureFuture.thenApply { texture ->
Filament.getInstance().assertIsFilamentThread()
val sampler = TextureSampler()
materialInstance.setParameter(name, texture, sampler)
}.exceptionally { e ->
println("Error loading texture: ${e.message}")
}
}
}
enum class PropertyType {
GRAPH_PROPERTY,
MATERIAL_PARAMETER
}
data class Property<T : PropertyValue>(
val name: String,
val value: T,
val type: PropertyType = PropertyType.GRAPH_PROPERTY,
// Construct an appropriate PropertyEditor for this property.
val editorFactory: (T) -> PropertyEditor
) {
fun callEditorFactory(): PropertyEditor = this.editorFactory.invoke(value)
}
fun <T : PropertyValue> copyPropertyWithValue(
p: Property<T>,
value: PropertyValue
): Property<*> =
Property(name = p.name, value = value as T, type = p.type, editorFactory = p.editorFactory)

View File

@@ -1,43 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
import com.google.android.filament.tungsten.model.NodeId
import java.awt.Point
data class NodeSchema(
val type: String,
val id: NodeId,
val position: Point? = Point(0, 0),
val properties: Map<String, Any>?
)
data class SlotSchema(val id: NodeId, val name: String)
data class ConnectionSchema(val from: SlotSchema, val to: SlotSchema) {
constructor(fromId: NodeId, fromSlot: String, toId: NodeId, toSlot: String):
this(SlotSchema(fromId, fromSlot), SlotSchema(toId, toSlot))
}
data class GraphSchema(
val nodes: List<NodeSchema>,
val rootNode: NodeId?,
val connections: List<ConnectionSchema>,
val version: String,
val editor: Map<String, Any> = emptyMap()
)

View File

@@ -1,31 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
object GraphFile {
fun addToolBlockToMaterialFile(materialFile: String, toolSectionContents: String): String {
return "$materialFile\n\ntool {\n$toolSectionContents\n}"
}
fun extractToolBlockFromMaterialFile(fileContents: String): String {
val result = "tool\\s*\\{(.*)}"
.toRegex(RegexOption.DOT_MATCHES_ALL)
.find(fileContents)?.groupValues?.get(1)
return result ?: ""
}
}

View File

@@ -1,104 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
import com.google.android.filament.tungsten.model.Connection
import com.google.android.filament.tungsten.model.Graph
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.NodeId
import com.google.android.filament.tungsten.model.copyPropertyWithValue
import java.awt.Point
/**
* An interface used by GraphSerializer that maps classes of NodeModels into String type identifiers
* for serialization. Given a type identifier, it can instantiate a new NodeModel of the correct
* type.
*/
interface INodeFactory {
fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node?
}
const val GRAPH_VERSION = "0.1"
object GraphSerializer {
fun serialize(
graph: Graph,
editorData: Map<String, Any>,
serializer: ISerializer = JsonSerializer()
): String {
val nodesToSerialize = graph.nodes.map { n ->
val properties = n.properties.mapNotNull {
val serialized = it.value.serialize()
if (serialized == null) null else it.name to serialized
}.toMap()
NodeSchema(n.type, n.id, Point(n.x.toInt(), n.y.toInt()), properties)
}
val connectionsToSerialize = graph.connections.map { c ->
val fromId = c.outputSlot.nodeId
val fromSlot = c.outputSlot.name
val toId = c.inputSlot.nodeId
val toSlot = c.inputSlot.name
ConnectionSchema(fromId, fromSlot, toId, toSlot)
}
return serializer.serialize(
GraphSchema(nodesToSerialize, graph.rootNodeId, connectionsToSerialize,
GRAPH_VERSION, editorData)
)
}
fun deserialize(
data: String,
nodeFactory: INodeFactory,
deserializer: IDeserializer = JsonDeserializer()
): Pair<Graph, Map<String, Any>> {
val (nodes, rootNode, connections, _, editor) = deserializer.deserialize(data)
val nodesInGraph = nodes.mapNotNull { n ->
val newNode = nodeFactory.createNodeForTypeIdentifier(n.type, n.id)
val position = n.position ?: Point(0, 0)
if (newNode == null) return@mapNotNull null
// Allow properties specified in the serialized data to override default properties
// of the node.
val overridenProperties = newNode.properties.map { p ->
val override = n.properties?.get(p.name) ?: p
val newValue = p.value.deserialize(override)
copyPropertyWithValue(p, newValue)
}
newNode.copy(
x = position.x.toFloat(),
y = position.y.toFloat(),
properties = overridenProperties)
}
val nodeMap = nodesInGraph.associateBy { n -> n.id }
val connectionsInGraph = connections.mapNotNull { c ->
// Only add connections if they refer to valid nodes in the graph
if (c.from.id in nodeMap && c.to.id in nodeMap)
Connection(Node.OutputSlot(c.from.id, c.from.name),
Node.InputSlot(c.to.id, c.to.name)) else null
}
val graph = Graph(nodes = nodesInGraph, rootNodeId = rootNode,
connections = connectionsInGraph)
return Pair(graph, editor)
}
}

View File

@@ -1,68 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonDeserializer
import com.google.gson.JsonSerializer
interface ISerializer {
fun serialize(graph: GraphSchema): String
}
interface IDeserializer {
fun deserialize(data: String): GraphSchema
}
// Serialize Slot as an array: [5, "foobar"]
val slotSerializer: JsonSerializer<SlotSchema> = JsonSerializer { src, _, _ ->
val pairArray = JsonArray()
pairArray.add(src.id)
pairArray.add(src.name)
pairArray
}
// Deserialize Slot from an array: [5, "foobar"]
val slotDeserializer: JsonDeserializer<SlotSchema> = JsonDeserializer { element, _, _ ->
val o = element.asJsonArray
val id = o.get(0).asInt
val slot = o.get(1).asString
SlotSchema(id, slot)
}
class JsonSerializer : ISerializer {
override fun serialize(graph: GraphSchema): String {
val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeAdapter(SlotSchema::class.java, slotSerializer)
val gson = gsonBuilder.create()
return gson.toJson(graph)
}
}
class JsonDeserializer : IDeserializer {
override fun deserialize(data: String): GraphSchema {
val gsonBuilder = GsonBuilder()
gsonBuilder.registerTypeAdapter(SlotSchema::class.java, slotDeserializer)
val gson = gsonBuilder.create()
return gson.fromJson(data, GraphSchema::class.java)
}
}

View File

@@ -1,25 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.properties
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.Property
interface IPropertiesPresenter {
fun propertyChanged(handle: Node.PropertyHandle, property: Property<*>)
}

View File

@@ -1,66 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.properties
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.NodeId
import com.google.android.filament.tungsten.model.copyPropertyWithValue
import javax.swing.JPanel
class PropertiesPanel : JPanel() {
var presenter: IPropertiesPresenter? = null
private val editorCache: MutableMap<NodeId, List<PropertyEditor>> = mutableMapOf()
fun showNone() {
removeAll()
repaint()
revalidate()
}
fun showPropertiesForNode(node: Node) {
val p = presenter ?: return
// TODO: every time a node is selected, we recreate the property panel for its
// properties. We could probably cache this somewhere and show / hide it to avoid
// re-adding components each time.
removeAll()
createEditorsForNode(node)
val editors = editorCache[node.id] ?: return
for ((property, editor) in node.properties.zip(editors)) {
editor.setValue(property.value)
editor.valueChanged = { newValue ->
p.propertyChanged(node.getPropertyHandle(property.name),
copyPropertyWithValue(property, newValue))
}
add(editor.component)
}
repaint()
revalidate()
}
/**
* editorCache caches a list of property editors for each node in the graph. This is done so
* property editors don't have to be re-allocated each time showPropertiesForNode is called.
*/
private fun createEditorsForNode(node: Node) {
editorCache.computeIfAbsent(node.id) { _ ->
node.properties.map { it.callEditorFactory() }
}
}
}

View File

@@ -1,196 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.properties
import com.google.android.filament.tungsten.model.Float3
import com.google.android.filament.tungsten.model.FloatValue
import com.google.android.filament.tungsten.model.PropertyValue
import com.google.android.filament.tungsten.model.StringValue
import com.google.android.filament.tungsten.model.TextureFile
import com.google.android.filament.tungsten.texture.TextureUtils
import java.awt.Color
import java.text.NumberFormat
import javax.swing.JButton
import javax.swing.JColorChooser
import javax.swing.JComboBox
import javax.swing.JComponent
import javax.swing.JFileChooser
import javax.swing.JFormattedTextField
import javax.swing.JPanel
import javax.swing.JSlider
import kotlin.math.roundToInt
abstract class PropertyEditor {
abstract val component: JComponent
var valueChanged: (newValue: PropertyValue) -> Unit = { }
abstract fun setValue(v: PropertyValue)
}
/**
* Set the value of a JSlider without causing any changeListeners to trigger.
*/
fun JSlider.quietlySetValue(v: Int) {
val changeListeners = this.changeListeners
for (changeListener in changeListeners) {
this.removeChangeListener(changeListener)
}
this.value = v
for (changeListener in changeListeners) {
this.addChangeListener(changeListener)
}
}
internal class FloatSlider(initialValue: FloatValue) : PropertyEditor() {
private val scaleFactor = 1000
override val component: JPanel = JPanel()
private val slider: JSlider
private val field: JFormattedTextField
private var currentValue: Float = initialValue.v
override fun setValue(v: PropertyValue) {
val value = v as FloatValue
currentValue = value.v
field.value = currentValue
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
}
init {
slider = JSlider(0, scaleFactor, (initialValue.v * scaleFactor).roundToInt())
slider.addChangeListener {
if (!slider.valueIsAdjusting) {
currentValue = slider.value.toFloat() / scaleFactor
updateValue()
}
}
val format = NumberFormat.getNumberInstance()
format.maximumFractionDigits = 5
field = JFormattedTextField(format)
field.columns = 10
field.value = initialValue.v
field.addActionListener {
val newValue = field.value as? Number
newValue?.let { v ->
currentValue = v.toFloat()
updateValue()
}
}
component.add(slider)
component.add(field)
}
private fun updateValue() {
slider.quietlySetValue((currentValue * scaleFactor).roundToInt())
field.value = currentValue
valueChanged(FloatValue(currentValue))
}
}
internal class ColorChooser(value: Float3) : PropertyEditor() {
override val component: JColorChooser
override fun setValue(v: PropertyValue) {
val value = v as Float3
component.setColor((value.x * 255.0f).roundToInt(), (value.y * 255.0f).roundToInt(),
(value.z * 255.0f).roundToInt())
}
init {
val initialColor = Color(value.x, value.y, value.z)
component = JColorChooser(initialColor)
component.selectionModel.addChangeListener {
val newValue = value.copy(
x = component.color.red / 255.0f,
y = component.color.green / 255.0f,
z = component.color.blue / 255.0f)
valueChanged(newValue)
}
}
}
internal class MultipleChoice(value: StringValue, choices: List<String>) : PropertyEditor() {
override val component = JComboBox<String>(choices.toTypedArray())
init {
component.addActionListener {
valueChanged(StringValue(component.selectedItem as String))
}
}
override fun setValue(v: PropertyValue) {
val newValue = v as? StringValue ?: return
component.selectedItem = newValue.value
}
}
internal class TextureFileChooser(initialValue: TextureFile) : PropertyEditor() {
private val colorSpaceToLabel = linkedMapOf(
TextureUtils.ColorSpaceStrategy.FORCE_SRGB to "sRGB",
TextureUtils.ColorSpaceStrategy.FORCE_LINEAR to "Linear",
TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE to "Use embedded file profile"
)
override val component: JPanel = JPanel()
private val fileChooser = JFileChooser()
private val colorSpaceChooser = JComboBox<String>(colorSpaceToLabel.values.toTypedArray())
private var textureFile = initialValue
override fun setValue(v: PropertyValue) {
val newValue = v as? TextureFile ?: return
textureFile = v
colorSpaceChooser.selectedItem = colorSpaceToLabel[newValue.colorSpace]
}
init {
val button = JButton("Choose file...")
colorSpaceChooser.selectedItem = colorSpaceToLabel[initialValue.colorSpace]
component.add(button)
component.add(colorSpaceChooser)
button.addActionListener {
val result = fileChooser.showOpenDialog(component)
if (result == JFileChooser.APPROVE_OPTION) {
textureFile = textureFile.copy(file = fileChooser.selectedFile)
valueChanged(textureFile)
}
}
colorSpaceChooser.addActionListener {
val newColorSpace = when (colorSpaceChooser.selectedIndex) {
0 -> TextureUtils.ColorSpaceStrategy.FORCE_SRGB
1 -> TextureUtils.ColorSpaceStrategy.FORCE_LINEAR
2 -> TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
else -> TextureUtils.ColorSpaceStrategy.USE_FILE_PROFILE
}
textureFile = textureFile.copy(colorSpace = newColorSpace)
valueChanged(textureFile)
}
}
}

View File

@@ -1,102 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.texture
import com.google.android.filament.Texture
import com.google.android.filament.tungsten.Filament
import java.io.File
import java.util.concurrent.CompletableFuture
typealias FutureTexture = CompletableFuture<Texture>
object TextureCache {
private data class TextureCacheKey(
val canonicalPath: String,
val colorSpace: TextureUtils.ColorSpaceStrategy
)
// Maps from canonical file path to Filament Texture
private val cache = mutableMapOf<TextureCacheKey, FutureTexture>()
private var defaultTexture: Texture? = null
// Only accessed from Filament thread
private var allowNewTextures = true
private val textures = mutableListOf<Texture>()
/**
* Loads the image into a Filament texture and returns a future that will be completed when the
* texture loads successfully or completed exceptionally if an error occurs.
*/
fun getTextureForFile(file: File, colorSpace: TextureUtils.ColorSpaceStrategy): FutureTexture {
val key = TextureCacheKey(file.canonicalPath, colorSpace)
cache.computeIfAbsent(key) {
createTextureForImageSource(file, colorSpace)
}
return cache[key] ?: getDefaultTexture()
}
fun getDefaultTexture(): FutureTexture {
defaultTexture?.let { return CompletableFuture.completedFuture(it) }
val futureTexture = FutureTexture()
Filament.getInstance().runOnFilamentThread { engine ->
val texture = TextureUtils.createDefaultTexture(engine)
futureTexture.complete(texture)
defaultTexture = texture
textures.add(texture)
}
return futureTexture
}
/*
* Keep track of texture and delete when Filament shutdownAndDestroyTextures is called.
*/
fun addTextureForRemoval(texture: Texture) {
Filament.getInstance().assertIsFilamentThread()
textures.add(texture)
}
/**
* Delete all cached textures and disallow any additional texture caching.
*/
fun shutdownAndDestroyTextures() {
Filament.getInstance().runOnFilamentThread { engine ->
for (texture in textures) {
engine.destroyTexture(texture)
}
allowNewTextures = false
}
}
private fun createTextureForImageSource(
imageSource: File,
colorSpace: TextureUtils.ColorSpaceStrategy
): FutureTexture {
val futureTexture = FutureTexture()
Filament.getInstance().runOnFilamentThread { engine ->
if (!allowNewTextures) return@runOnFilamentThread
val texture = TextureUtils.loadTextureFromFile(engine, imageSource, colorSpace)
if (texture != null) {
futureTexture.complete(texture)
textures.add(texture)
} else {
futureTexture.completeExceptionally(RuntimeException("Unable to load texture."))
}
}
return futureTexture
}
}

View File

@@ -1,197 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.texture
import com.google.android.filament.Engine
import com.google.android.filament.Texture
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import java.awt.image.BufferedImage.TYPE_3BYTE_BGR
import java.awt.image.BufferedImage.TYPE_4BYTE_ABGR
import java.awt.image.BufferedImage.TYPE_4BYTE_ABGR_PRE
import java.awt.image.BufferedImage.TYPE_INT_ARGB
import java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE
import java.awt.image.BufferedImage.TYPE_INT_BGR
import java.awt.image.DataBufferByte
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
private fun Texture.InternalFormat.toSrgb(): Texture.InternalFormat {
return when (this) {
Texture.InternalFormat.RGB8 -> Texture.InternalFormat.SRGB8
Texture.InternalFormat.RGBA8 -> Texture.InternalFormat.SRGB8_A8
else -> this
}
}
data class ImageInfo(val width: Int, val height: Int)
object TextureUtils {
enum class ColorSpaceStrategy {
// Use the profile present in the image file, defaulting to sRGB if none exists.
USE_FILE_PROFILE,
// Always assume an sRGB color space.
FORCE_SRGB,
// Always assume a non-sRGB color space.
FORCE_LINEAR
}
fun createDefaultTexture(engine: Engine): Texture {
val stream = javaClass.classLoader.getResourceAsStream("checkerboard.png")
val texture = stream?.let { s ->
ImageIO.read(s)?.let {
img -> loadTextureFromImage(engine, img, ColorSpaceStrategy.FORCE_LINEAR)
}
}
return texture ?: throw RuntimeException("Could not load default texture")
}
fun loadTextureFromFile(engine: Engine, file: File, colorSpace: ColorSpaceStrategy): Texture? {
val img = try {
ImageIO.read(file) ?: return null
} catch (e: IOException) {
System.err.println("Could not read image ${file.canonicalPath}.")
e.printStackTrace()
return null
}
return loadTextureFromImage(engine, img, colorSpace)
}
fun loadImageBufferFromStream(stream: InputStream): Pair<ByteBuffer, ImageInfo>? {
val img = try {
ImageIO.read(stream) ?: return null
} catch (e: IOException) {
System.err.println("Could not parse image from InputStream.")
e.printStackTrace()
return null
}
return Pair(loadImageBuffer(img), ImageInfo(img.width, img.height))
}
/**
* Based on the number of components the image has and its color space, decide which
* texture formats to use.
*/
private fun decideTextureFormat(
img: BufferedImage,
colorSpace: ColorSpaceStrategy
): Pair<Texture.InternalFormat, Texture.Format> {
val isSrgb = when (colorSpace) {
ColorSpaceStrategy.FORCE_LINEAR -> false
ColorSpaceStrategy.FORCE_SRGB -> true
ColorSpaceStrategy.USE_FILE_PROFILE -> img.colorModel.colorSpace.isCS_sRGB
}
val (internalFormat, textureFormat) = when (img.colorModel.numComponents) {
1 -> Texture.InternalFormat.R8 to Texture.Format.R
2 -> Texture.InternalFormat.RG8 to Texture.Format.RG
3 -> Texture.InternalFormat.RGB8 to Texture.Format.RGB
4 -> Texture.InternalFormat.RGBA8 to Texture.Format.RGBA
else -> Texture.InternalFormat.RGBA8 to Texture.Format.RGBA
}
return (if (isSrgb) internalFormat.toSrgb() else internalFormat) to textureFormat
}
private fun loadImageBuffer(img: BufferedImage): ByteBuffer {
val data = img.raster.dataBuffer as DataBufferByte
flipComponentsIfNecessary(img)
return ByteBuffer.wrap(data.data)
}
private fun loadTextureFromImage(
engine: Engine,
img: BufferedImage,
colorSpace: ColorSpaceStrategy
): Texture? {
val data = img.raster.dataBuffer as DataBufferByte
val (internalFormat, textureFormat) = decideTextureFormat(img, colorSpace)
flipComponentsIfNecessary(img)
val texture = Texture.Builder()
.width(img.width)
.height(img.height)
.format(internalFormat)
.sampler(Texture.Sampler.SAMPLER_2D)
.build(engine)
val buf = ByteBuffer.wrap(data.data)
val desc = Texture.PixelBufferDescriptor(buf, textureFormat, Texture.Type.UBYTE)
texture.setImage(engine, Texture.BASE_LEVEL, desc)
return texture
}
private fun flipComponentsIfNecessary(img: BufferedImage) {
val data = img.raster.dataBuffer as DataBufferByte
val pixels = data.data
val components = img.colorModel.numComponents
// Only flip components if they are specified in non-RGBA order.
val typesToFlip = listOf(
TYPE_INT_ARGB,
TYPE_INT_ARGB_PRE,
TYPE_INT_BGR,
TYPE_3BYTE_BGR,
TYPE_4BYTE_ABGR,
TYPE_4BYTE_ABGR_PRE
)
if (!typesToFlip.contains(img.type)) {
return
}
if (components == 4) {
// ABGR -> RGBA
assert(pixels.size % 4 == 0)
var i = 0
while (i < pixels.size) {
val A = pixels[i]
val B = pixels[i + 1]
val G = pixels[i + 2]
val R = pixels[i + 3]
pixels[i] = R
pixels[i + 1] = G
pixels[i + 2] = B
pixels[i + 3] = A
i += 4
}
}
if (components == 3) {
// BGR -> RGB
assert(pixels.size % 3 == 0)
var i = 0
while (i < pixels.size) {
val B = pixels[i]
val G = pixels[i + 1]
val R = pixels[i + 2]
pixels[i] = R
pixels[i + 1] = G
pixels[i + 2] = B
i += 3
}
}
}
}

View File

@@ -1,29 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import java.awt.Color
object ColorScheme {
val background = Color(60, 63, 65)
val connectionLine = Color.WHITE
val slotLabel = Color.BLACK
val nodeBackground = Color.LIGHT_GRAY
val nodeSelected = Color.YELLOW
val slotCircle = Color.GREEN
}

View File

@@ -1,97 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import com.google.android.filament.tungsten.SwingHelper
import com.google.android.filament.tungsten.model.Slot
import java.awt.BasicStroke
import java.awt.Color
import java.awt.Graphics2D
import java.awt.geom.GeneralPath
import java.awt.geom.Point2D
/**
* Endpoint provides either a start or end point to a ConnectionLine.
*/
typealias Endpoint = () -> Point2D.Double?
internal fun slotPoint(slot: Slot, graph: MaterialGraphComponent): Endpoint {
return fun (): Point2D.Double? {
val graphView = graph.graphView ?: return null
val slotView = graph.getSlotCircleForSlot(slot) ?: return null
val centerPoint = convertPoint(slotView, slotView.centerPoint, graphView)
if (centerPoint != null) {
return Point2D.Double(centerPoint.x, centerPoint.y)
}
return null
}
}
internal fun arbitraryPoint(x: Double, y: Double): Endpoint {
return {
Point2D.Double(x, y)
}
}
/**
* ConnectionLine is a line drawn between two slots, or if a connection is being formed, between a
* slot and an arbitrary point.
*/
class ConnectionLine(from: Endpoint, to: Endpoint) {
var outputPoint = from
var inputPoint = to
fun paint(g2d: Graphics2D) {
SwingHelper.setRenderingHints(g2d)
g2d.color = ColorScheme.connectionLine
val originPoint = outputPoint() ?: return
val destinationPoint = inputPoint() ?: return
val deltaX = Math.abs(destinationPoint.getX() - originPoint.getX())
val p2 = Point2D.Double(originPoint.getX() + deltaX / 2.0, originPoint.getY())
val p3 = Point2D.Double(destinationPoint.getX() - deltaX / 2, destinationPoint.getY())
drawBezierCurve(g2d, originPoint, p2, p3, destinationPoint)
}
/**
* Draw a 4 control point Bezier curve.
*/
fun drawBezierCurve(
g2d: Graphics2D,
p1: Point2D.Double,
p2: Point2D.Double,
p3: Point2D.Double,
p4: Point2D.Double
) {
val path = GeneralPath()
path.reset()
path.moveTo(p1.x, p1.y)
path.curveTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y)
val actionStroke = BasicStroke(2f)
g2d.stroke = actionStroke
g2d.color = Color.WHITE
g2d.draw(path)
}
}

View File

@@ -1,94 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
public class GraphPopup implements MouseListener, ActionListener {
private GraphPresenter mPresenter;
private final JPopupMenu mMenu;
private int mPopupXLocation;
private int mPopupYLocation;
/**
* Adds a Popup menu and calls actions in the presenter when items are selected.
* @param component The component to add the popup menu to.
*/
public GraphPopup(JComponent component, List<String> items) {
// Create a menu and for each node registered in NodeRegistry, create a corresponding item
mMenu = new JPopupMenu();
for (String nodeName : items) {
JMenuItem menuItem = new JMenuItem(nodeName);
mMenu.add(menuItem);
menuItem.addActionListener(this);
}
component.addMouseListener(this);
}
public void setPresenter(GraphPresenter presenter) {
mPresenter = presenter;
}
@Override
public void mouseClicked(MouseEvent e) {
checkAndTriggerPopup(e);
}
@Override
public void mousePressed(MouseEvent e) {
checkAndTriggerPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
checkAndTriggerPopup(e);
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
private void checkAndTriggerPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
mMenu.show(e.getComponent(), e.getX(), e.getY());
mPopupXLocation = e.getX();
mPopupYLocation = e.getY();
}
}
@Override
public void actionPerformed(ActionEvent e) {
// The action command is the text displayed on the JMenuItem
String nodeName = e.getActionCommand();
mPresenter.popupMenuItemSelected(nodeName, mPopupXLocation, mPopupYLocation);
}
}

View File

@@ -1,256 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import com.google.android.filament.Material;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.tungsten.Filament;
import com.google.android.filament.tungsten.MaterialManager;
import com.google.android.filament.tungsten.compiler.CompiledGraph;
import com.google.android.filament.tungsten.compiler.GraphCompiler;
import com.google.android.filament.tungsten.compiler.NodeRegistry;
import com.google.android.filament.tungsten.compiler.Parameter;
import com.google.android.filament.tungsten.model.Connection;
import com.google.android.filament.tungsten.model.Graph;
import com.google.android.filament.tungsten.model.GraphInitializer;
import com.google.android.filament.tungsten.model.Node;
import com.google.android.filament.tungsten.model.Property;
import com.google.android.filament.tungsten.model.PropertyType;
import com.google.android.filament.tungsten.model.serialization.GraphFile;
import com.google.android.filament.tungsten.model.serialization.GraphSerializer;
import com.google.android.filament.tungsten.model.serialization.JsonDeserializer;
import com.google.android.filament.tungsten.model.serialization.JsonSerializer;
import com.google.android.filament.tungsten.properties.IPropertiesPresenter;
import com.google.android.filament.tungsten.properties.PropertiesPanel;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JTextArea;
import com.google.android.filament.tungsten.ui.preview.PreviewMeshPanel;
import org.jetbrains.annotations.Nullable;
public class GraphPresenter implements IPropertiesPresenter {
// Views
private final MaterialGraphComponent mGraphView;
private final PreviewMeshPanel mPreviewMeshPanel;
private final JTextArea mMaterialSource;
private final PropertiesPanel mPropertiesPanel;
// Dependencies
private final NodeRegistry mNodeRegistry;
private final MaterialManager mMaterialManager;
private final TungstenFile mFile;
private CompiledGraph mCompiledGraph;
// Technically volatile is enough here b/c the Filament thread only ever reads
private AtomicReference<Graph> mModel = new AtomicReference<>();
// Only accessed from Filament thread
@Nullable private MaterialInstance mCurrentMaterialInstance = null;
public GraphPresenter(MaterialGraphComponent graphView, PreviewMeshPanel previewMeshPanel,
JTextArea materialSource, PropertiesPanel propertiesPanel,
MaterialManager materialManager, TungstenFile file) {
mGraphView = graphView;
mPreviewMeshPanel = previewMeshPanel;
mMaterialSource = materialSource;
mPropertiesPanel = propertiesPanel;
mMaterialManager = materialManager;
mNodeRegistry = new NodeRegistry();
mFile = file;
loadModelFromFile(file);
}
/**
* Action from the view that lets us know a connection between two nodes has been created.
*/
public void connectionCreated(Connection connection) {
mModel.getAndUpdate(graph -> graph.graphByFormingConnection(connection));
mGraphView.render(mModel.get());
recompileGraph();
}
/**
* Action from the view that lets us know a connection between two nodes has been removed.
*/
public void connectionDisconnectedAtInput(Connection connection) {
mModel.getAndUpdate(graph -> graph.graphByRemovingConnection(connection));
mGraphView.render(mModel.get());
recompileGraph();
}
/**
* Action from the view that lets us know the selection of nodes has changed.
*/
public void selectionChanged(List<Integer> selectedNodes) {
mModel.getAndUpdate(graph -> graph.graphByChangingSelection(selectedNodes));
mGraphView.render(mModel.get());
if (selectedNodes.size() != 1) {
mPropertiesPanel.showNone();
return;
}
mPropertiesPanel.showPropertiesForNode(mModel.get().getNodeWithId(selectedNodes.get(0)));
}
/**
* Action from the view that lets us know a node has been dragged to a new location.
*/
public void nodeMoved(Node node, int x, int y) {
mModel.getAndUpdate(graph -> graph.graphByMovingNode(node, x, y));
mGraphView.render(mModel.get());
serializeAndSave();
}
/**
* Action from the view that lets us know a NodeProperty's value has changed.
*/
@Override
public void propertyChanged(Node.PropertyHandle handle, Property property) {
mModel.getAndUpdate(graph -> graph.graphByChangingProperty(handle, property));
mGraphView.render(mModel.get());
mPropertiesPanel.showPropertiesForNode(mModel.get().getSelectedNodes().get(0));
// Graph property changes trigger a full recompilation of the graph, while material
// parameter changes trigger only a material parameter update on the current material
// instance.
if (property.getType() == PropertyType.GRAPH_PROPERTY) {
recompileGraph();
} else if (property.getType() == PropertyType.MATERIAL_PARAMETER) {
serializeAndSave();
Parameter parameter = mCompiledGraph.getParameterMap().get(handle);
if (parameter == null) {
return;
}
Filament.getInstance().runOnFilamentThread(engine -> {
if (mCurrentMaterialInstance == null) {
return;
}
// This check prevents us from setting a property on a newer material instance that
// may no longer have that property.
if (mCurrentMaterialInstance.getMaterial().hasParameter(parameter.getName())) {
property.getValue().applyToMaterialInstance(mCurrentMaterialInstance,
parameter.getName());
}
});
}
}
/**
* Action from the popup menu that lets us know an item has been clicked.
*/
public void popupMenuItemSelected(String name, int x, int y) {
Node newNode = mNodeRegistry.createNodeForLabel(name, mModel.get().getNewNodeId());
if (newNode == null) {
return;
}
mModel.getAndUpdate(graph -> graph.graphByAddingNodeAtLocation(newNode, x, y));
recompileGraph();
mGraphView.render(mModel.get());
}
private void loadModelFromFile(TungstenFile file) {
file.read((success, contents) -> {
if (!success) {
// TODO: propagate this error to the user somehow in the UI
System.out.println("Unable to read graph file.");
return;
}
String toolBlock = GraphFile.INSTANCE.extractToolBlockFromMaterialFile(contents);
if (toolBlock.isEmpty()) {
mModel.set(GraphInitializer.INSTANCE.getInitialGraphState());
recompileGraph();
mGraphView.render(mModel.get());
return;
}
mModel.set(GraphSerializer.INSTANCE.deserialize(
toolBlock, mNodeRegistry, new JsonDeserializer()).getFirst());
mGraphView.render(mModel.get());
recompileGraph();
});
}
private void recompileGraph() {
if (mModel.get().getRootNode() == null) {
return;
}
GraphCompiler compiler = new GraphCompiler(mModel.get());
mCompiledGraph = compiler.compileGraph();
mMaterialSource.setText(mCompiledGraph.getMaterialDefinition());
CompletableFuture<Material> futureMaterial = mMaterialManager
.compileMaterial(mCompiledGraph.getMaterialDefinition());
// Update the model with the newly compiled expression map and replace any modified nodes.
mModel.getAndUpdate(
graph -> graph
.graphBySettingExpressionMap(mCompiledGraph.getExpressionMap())
.graphByReplacingNodes(mCompiledGraph.getOldToNewNodeMap()));
mGraphView.render(mModel.get());
serializeAndSave();
final CompiledGraph compiledGraph = mCompiledGraph;
futureMaterial.thenAccept(newMaterial -> {
Filament.getInstance().assertIsFilamentThread();
mCurrentMaterialInstance = newMaterial.createInstance();
mPreviewMeshPanel.updateMaterial(mCurrentMaterialInstance);
// For all parameters present in the compiled graph, update them based on the current
// value in our model.
for (Map.Entry<Node.PropertyHandle, Parameter> entry
: compiledGraph.getParameterMap().entrySet()) {
Node.PropertyHandle handle = entry.getKey();
Parameter parameter = entry.getValue();
Property nodeProperty = mModel.get().getNodeProperty(handle);
if (nodeProperty == null) {
continue;
}
if (mCurrentMaterialInstance.getMaterial().hasParameter(parameter.getName())) {
nodeProperty.getValue()
.applyToMaterialInstance(mCurrentMaterialInstance, parameter.getName());
}
}
});
}
private void serializeAndSave() {
if (mCompiledGraph == null) {
return;
}
String serializedGraph = GraphSerializer.INSTANCE.serialize(mModel.get(),
Collections.emptyMap(), new JsonSerializer());
String materialDefinition =
GraphFile.INSTANCE.addToolBlockToMaterialFile(
mCompiledGraph.getMaterialDefinition(), serializedGraph);
mFile.write(materialDefinition, success -> {
if (!success) {
// TODO: propagate this error to the user somehow in the UI
System.out.println("Unable to write graph file.");
}
});
}
}

View File

@@ -1,128 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import java.awt.Graphics2D
import java.awt.geom.Point2D
private const val SLOT_CIRCLE_RADIUS = 10.0f
private const val SLOT_TEXT_PAD = 5.0f
private const val ARC_RADIUS = 10
private const val BORDER_THICKNESS = 2
private const val NODE_HEIGHT = 100.0f
private const val NODE_WIDTH = 200.0f
private const val SLOT_MARGIN = 10.0f
private const val SLOT_HEIGHT = 20.0f
internal class SlotCircle(val parent: SlotView) : View() {
val centerPoint = Point2D.Float(SLOT_CIRCLE_RADIUS / 2, SLOT_CIRCLE_RADIUS / 2)
override fun render(g2d: Graphics2D) {
g2d.color = ColorScheme.slotCircle
g2d.fillOval(0, 0, SLOT_CIRCLE_RADIUS.toInt(), SLOT_CIRCLE_RADIUS.toInt())
}
}
internal data class SlotView(
val label: String,
val isInput: Boolean,
val onConnectionDragStart: (SlotView) -> Unit,
val onConnectionDragEnd: (SlotView) -> Unit
) : View() {
val circle = SlotCircle(this)
override val children = listOf(circle)
override val reactToMouseEvents = false
override fun layout() {
if (isInput) {
circle.x = 0.0f
circle.y = 0.0f
} else {
circle.x = width - SLOT_CIRCLE_RADIUS
circle.y = 0.0f
}
circle.width = SLOT_CIRCLE_RADIUS
circle.height = SLOT_CIRCLE_RADIUS
}
override fun render(g2d: Graphics2D) {
g2d.color = ColorScheme.slotLabel
if (isInput) {
g2d.drawString(label, SLOT_CIRCLE_RADIUS + SLOT_TEXT_PAD, g2d.fontMetrics.height / 2.0f)
} else {
val textWidth = g2d.fontMetrics.stringWidth(label)
g2d.drawString(label, (width - SLOT_CIRCLE_RADIUS - SLOT_TEXT_PAD) - textWidth,
g2d.fontMetrics.height / 2.0f)
}
}
}
internal data class NodeView(
override var x: Float,
override var y: Float,
val inputSlots: List<SlotView>,
val outputSlots: List<SlotView>,
val isSelected: Boolean,
val nodeDragStarted: (NodeView) -> Unit,
val nodeDragStopped: (NodeView) -> Unit,
val nodeClicked: () -> Unit
) : View() {
override val children = inputSlots + outputSlots
override fun layout() {
width = NODE_WIDTH
height = maxOf((inputSlots.size + outputSlots.size) * SLOT_HEIGHT + 2 * SLOT_MARGIN,
NODE_HEIGHT)
inputSlots.forEachIndexed { index, slot ->
slot.x = SLOT_MARGIN
slot.y = SLOT_MARGIN + index * SLOT_HEIGHT
slot.width = width - SLOT_MARGIN * 2
slot.height = SLOT_HEIGHT
}
outputSlots.forEachIndexed { index, slot ->
slot.x = SLOT_MARGIN
slot.y = SLOT_MARGIN + (index + inputSlots.size) * SLOT_HEIGHT
slot.width = width - SLOT_MARGIN * 2
slot.height = SLOT_HEIGHT
}
}
override fun render(g2d: Graphics2D) {
if (isSelected) {
g2d.color = ColorScheme.nodeSelected
g2d.fillRoundRect(0, 0, width.toInt(), height.toInt(), ARC_RADIUS, ARC_RADIUS)
}
g2d.color = ColorScheme.nodeBackground
g2d.fillRoundRect(BORDER_THICKNESS, BORDER_THICKNESS,
(width - 2 * BORDER_THICKNESS).toInt(),
(height - 2 * BORDER_THICKNESS).toInt(), ARC_RADIUS, ARC_RADIUS)
}
}
internal data class GraphView(
val nodes: List<NodeView>,
override var width: Float,
override var height: Float
) : View() {
override val children = nodes
}

View File

@@ -1,35 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import javax.swing.JComponent;
public interface GuiComponentFactory {
/**
* Creates a Panel comprised of 3 components separated by platform-specific splitters.
* _________
* | | |
* | | 2 |
* | 1 |____|
* | | |
* | | 3 |
* |____|____|
*/
JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
JComponent third);
}

View File

@@ -1,335 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import com.google.android.filament.tungsten.SwingHelper
import com.google.android.filament.tungsten.model.Connection
import com.google.android.filament.tungsten.model.Graph
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.NodeId
import com.google.android.filament.tungsten.model.Slot
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.event.MouseEvent
import java.util.ArrayList
import java.util.HashMap
import javax.swing.JComponent
class MaterialGraphComponent : JComponent(), TungstenMouseListener {
private val mEventManager = TungstenEventManager(this)
private var mNodeBeingDragged: NodeId? = null
private var mPreviousSelectionModel: List<Int> = ArrayList()
private var mIsConnecting = false
// A connection is "in progress" when a user has started dragging from a slot
private var mInProgressConnectionLine: ConnectionLine? = null
// The slot the user started dragging from when forming a connection
private var mInProgressOriginSlot: Slot? = null
private val mConnectionLines = ArrayList<ConnectionLine>()
private var mPresenter: GraphPresenter? = null
// Stores the current node views being shown
private val mNodeViews = mutableListOf<NodeView>()
// Stores the current graph displayed in the component, or null if one hasn't been rendered yet
private var mGraph: Graph? = null
internal var graphView: GraphView? = null
// Maps between the Nodes and the associated NodeViews that represent them
private val nodeMap = HashMap<NodeId, NodeView>()
// Maps between the SlotModels we receive and the NodeSlot views
private val mSlotMap = HashMap<Slot, SlotCircle>()
// Maps between the ConnectionModels we receive and our views
private val mConnectionViewMap = HashMap<Connection, ConnectionLine>()
init {
addMouseMotionListener(mEventManager)
addMouseListener(mEventManager)
}
fun setPresenter(presenter: GraphPresenter) {
mPresenter = presenter
}
private fun renderGraph(graph: Graph): GraphView {
mSlotMap.clear()
nodeMap.clear()
val renderSlot = { node: Node, slotName: String, isInput: Boolean ->
val slotHandle = if (isInput) {
node.getInputSlot(slotName)
} else {
node.getOutputSlot(slotName)
}
val expression = graph.expressionMap[slotHandle]
val slotView = SlotView(
label = if (expression != null)
"$slotName (${expression.dimensions})" else slotName,
isInput = isInput,
onConnectionDragStart = { this.nodeSlotDragStarted(slotHandle) },
onConnectionDragEnd = { this.nodeSlotDragEnded(slotHandle) }
)
mSlotMap[slotHandle] = slotView.circle
slotView
}
val renderNode = { node: Node ->
val nodeView = NodeView(
x = node.x,
y = node.y,
inputSlots = node.inputSlots.map { slotName ->
renderSlot(node, slotName, true)
},
outputSlots = node.outputSlots.map { slotName ->
renderSlot(node, slotName, false)
},
isSelected = graph.isNodeSelected(node),
nodeDragStarted = { view ->
mNodeBeingDragged = node.id
notifySelectionIfChanged(listOf(node.id))
},
nodeDragStopped = { view ->
// We've just finished dragging a node, so alert the presenter
mPresenter?.nodeMoved(node, view.x.toInt(), view.y.toInt())
mNodeBeingDragged = null
},
nodeClicked = {
notifySelectionIfChanged(listOf(node.id))
}
)
nodeMap[node.id] = nodeView
nodeView
}
return GraphView(
nodes = graph.nodes.map { renderNode(it) },
width = this.width.toFloat(),
height = this.height.toFloat()
)
}
fun render(graph: Graph) {
// Because graphs are immutable, if the graph we previously rendered is the same graph
// object, we have no work to do.
if (mGraph === graph) {
return
}
val graphView = renderGraph(graph)
this.graphView = graphView
mNodeViews.clear()
mNodeViews.addAll(graphView.nodes)
// Create a connection line for each connection in the graph.
mConnectionLines.clear()
mConnectionViewMap.clear()
for (connection in graph.connections) {
val outputSlot = connection.outputSlot
val inputSlot = connection.inputSlot
val line = ConnectionLine(slotPoint(outputSlot, this),
slotPoint(inputSlot, this))
mConnectionLines.add(line)
mConnectionViewMap[connection] = line
}
// Layout the rendered views so they have accurate bounds. This must be done here and not in
// paintComponent, as paintComponent might not be called immediately.
layoutHierarchy(graphView)
revalidate()
repaint()
mGraph = graph
}
override fun mousePressed(e: MouseEvent) {
// Check if we've clicked on a MaterialNode.
val node = graphView?.let { graphView ->
val view = findViewAt(graphView, e.point)
view as? NodeView?
}
if (node == null) {
// If we didn't click on a MaterialNode, clear the selection
deselectNodes()
return
}
node.nodeClicked()
}
override fun mouseDragStarted(e: MouseEvent) {
// If we've started dragging over a slot circle, we've started forming a new connection
val graphView = graphView ?: return
val view = findViewAt(graphView, e.point) ?: return
when (view) {
is SlotCircle -> view.parent.onConnectionDragStart(view.parent)
is NodeView -> view.nodeDragStarted(view)
}
}
override fun mouseDragEnded(e: MouseEvent) {
mNodeBeingDragged?.let { nodeId ->
val nodeView = nodeMap[nodeId]
if (nodeView != null) {
nodeView.nodeDragStopped(nodeView)
}
}
if (!mIsConnecting) {
return
}
mIsConnecting = false
repaint()
val graphView = graphView ?: return
val view = findViewAt(graphView, e.point) ?: return
when (view) {
is SlotCircle -> view.parent.onConnectionDragEnd(view.parent)
}
}
override fun mouseDragged(e: MouseEvent) {
val nodeId = mNodeBeingDragged
if (nodeId != null) {
val nodeView = nodeMap[nodeId]
if (nodeView != null) {
nodeView.x += mEventManager.mouseDeltaX
nodeView.y += mEventManager.mouseDeltaY
repaint()
return
}
}
nodeSlotDragged(e)
}
override fun paintComponent(g: Graphics) {
super.paintComponent(g)
val g2d = g as Graphics2D
SwingHelper.setRenderingHints(g2d)
// Paint background
g2d.color = ColorScheme.background
g2d.fillRect(0, 0, width, height)
// If we're currently connecting two slots, draw the connection
if (mIsConnecting) {
mInProgressConnectionLine?.paint(g2d)
}
mConnectionLines.forEach { connection -> connection.paint(g2d) }
// Render graph
graphView?.let { graphView -> renderHierarchy(graphView, g2d) }
}
internal fun getSlotCircleForSlot(slot: Slot) = mSlotMap[slot]
private fun nodeSlotDragStarted(slot: Slot) {
// If the user starts dragging on an input slot that already has a connection, remove the
// previous connection
val graph = mGraph ?: return
for (connection in graph.connections) {
if (connection.inputSlot == slot) {
// First, remove the connection view
val connectionLine = mConnectionViewMap.remove(connection) ?: return
mConnectionLines.remove(connectionLine)
mInProgressConnectionLine = connectionLine
// Create an in-progress connection by disconnecting the destination
mInProgressOriginSlot = connection.outputSlot
mIsConnecting = true
mPresenter?.connectionDisconnectedAtInput(connection)
return
}
}
// Create a new connection line to represent this new "in progress" connection
mInProgressConnectionLine = ConnectionLine(slotPoint(slot, this),
slotPoint(slot, this))
mInProgressOriginSlot = slot
mIsConnecting = true
repaint()
}
private fun nodeSlotDragged(e: MouseEvent) {
if (!mIsConnecting || mInProgressConnectionLine == null) {
return
}
// Depending on what type of slot the user is dragging from, the mouse is updating the
// input or output point
if (mInProgressOriginSlot is Node.InputSlot) {
mInProgressConnectionLine?.outputPoint = arbitraryPoint(e.x.toDouble(), e.y.toDouble())
} else {
mInProgressConnectionLine?.inputPoint = arbitraryPoint(e.x.toDouble(), e.y.toDouble())
}
repaint()
}
private fun nodeSlotDragEnded(slot: Slot) {
var output = mInProgressOriginSlot ?: return
var input = slot
mIsConnecting = false
// If the user started dragging on an input slot, they're connecting the nodes "backwards"
// from input to output, so swap output and input.
if (mInProgressOriginSlot is Node.InputSlot) {
val temp = output
output = input
input = temp
}
val outputSlot = output as? Node.OutputSlot ?: return
val inputSlot = input as? Node.InputSlot ?: return
val newConnectionModel = Connection(outputSlot, inputSlot)
mPresenter?.connectionCreated(newConnectionModel)
repaint()
}
private fun deselectNodes() {
notifySelectionIfChanged(emptyList())
}
/**
* If selectionModel has changed, notify the presenter. Here we compare against the previous
* selection in order to avoid notifying the presenter twice for the same selection (e.g. if the
* user continuously clicks on the same node)
*/
private fun notifySelectionIfChanged(selectedNodes: List<Int>) {
if (selectedNodes == mPreviousSelectionModel) {
return
}
mPresenter?.selectionChanged(selectedNodes)
mPreviousSelectionModel = selectedNodes
}
}

View File

@@ -1,101 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
/**
* TungstenEventManager marshals events from both the MouseListener and MouseMotionListener
* interfaces to a single TungstenMouseListener.
*
* Classes should instantiate a TungsteneEventManager and add it as a both a MouseListener and
* MouseMotionListener.
*/
class TungstenEventManager implements MouseListener, MouseMotionListener {
private TungstenMouseListener mListener;
private int mMousePreviousX;
private int mMousePreviousY;
private int mMouseDeltaX;
private int mMouseDeltaY;
private boolean mMouseIsDragging = false;
TungstenEventManager(TungstenMouseListener listener) {
mListener = listener;
}
int getMouseDeltaX() {
return mMouseDeltaX;
}
int getMouseDeltaY() {
return mMouseDeltaY;
}
@Override
public void mouseClicked(MouseEvent e) {
mListener.mouseClicked(e);
}
@Override
public void mousePressed(MouseEvent e) {
mListener.mousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e) {
if (mMouseIsDragging) {
mMouseIsDragging = false;
mListener.mouseDragEnded(e);
}
}
@Override
public void mouseEntered(MouseEvent e) {
mListener.mouseEntered(e);
}
@Override
public void mouseExited(MouseEvent e) {
mListener.mouseExited(e);
}
@Override
public void mouseDragged(MouseEvent e) {
calculateMouseDelta(e);
if (!mMouseIsDragging) {
mMouseIsDragging = true;
mListener.mouseDragStarted(e);
}
mListener.mouseDragged(e);
}
@Override
public void mouseMoved(MouseEvent e) {
calculateMouseDelta(e);
mListener.mouseMoved(e);
}
private void calculateMouseDelta(MouseEvent e) {
mMouseDeltaX = e.getX() - mMousePreviousX;
mMouseDeltaY = e.getY() - mMousePreviousY;
mMousePreviousX = e.getX();
mMousePreviousY = e.getY();
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import java.util.function.BiConsumer
import java.util.function.Consumer
interface TungstenFile {
/**
* Write new contents to the file, replacing any existing contents.
* @param callback is called with true if the write is successful, false otherwise.
*/
fun write(contents: String, callback: Consumer<Boolean>)
/**
* Read contents of a file asynchronously. result callback will be called with a boolean
* representing success and if successful, the full contents of the file.
*/
fun read(result: BiConsumer<Boolean, String?>)
}

View File

@@ -1,65 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import java.awt.event.MouseEvent;
/**
* TungstenMouseListener combines Swing's MouseListener and MouseMotionListener as well as adding
* some helpful events, like mouseDragStarted and mouseDragEnded.
*
* An empty default implementation for each method is provided so classes that adhere are not
* required to override every method.
*/
public interface TungstenMouseListener {
default void mouseDragged(MouseEvent e) {
}
default void mouseDragStarted(MouseEvent e) {
}
default void mouseDragEnded(MouseEvent e) {
}
default void mouseMoved(MouseEvent e) {
}
default void mouseClicked(MouseEvent e) {
}
default void mousePressed(MouseEvent e) {
}
default void mouseReleased(MouseEvent e) {
}
default void mouseEntered(MouseEvent e) {
}
default void mouseExited(MouseEvent e) {
}
}

View File

@@ -1,72 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui;
import com.google.android.filament.tungsten.MaterialManager;
import com.google.android.filament.tungsten.compiler.NodeRegistry;
import com.google.android.filament.tungsten.properties.PropertiesPanel;
import com.google.android.filament.tungsten.ui.preview.PreviewMeshPanel;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
public class TungstenPanel extends JPanel {
private PreviewMeshPanel mPreviewMeshPanel;
public TungstenPanel(GuiComponentFactory componentFactory, MaterialManager materialManager,
TungstenFile file) {
BorderLayout panelLayout = new BorderLayout();
setLayout(panelLayout);
mPreviewMeshPanel = new PreviewMeshPanel();
mPreviewMeshPanel.setMinimumSize(new Dimension(0, 300));
JTextArea materialSource = new JTextArea();
materialSource.setEditable(false);
MaterialGraphComponent materialGraph = new MaterialGraphComponent();
PropertiesPanel propertiesPanel = new PropertiesPanel();
propertiesPanel.setMinimumSize(new Dimension(500, 200));
NodeRegistry registry = new NodeRegistry();
GraphPopup popup = new GraphPopup(materialGraph, registry.getNodeLabelsForMenu());
JTabbedPane editor = new JTabbedPane();
editor.addTab("Graph", materialGraph);
editor.addTab("Source", materialSource);
GraphPresenter presenter = new GraphPresenter(materialGraph, mPreviewMeshPanel,
materialSource, propertiesPanel, materialManager, file);
materialGraph.setPresenter(presenter);
propertiesPanel.setPresenter(presenter);
popup.setPresenter(presenter);
add(componentFactory.createThreeComponentSplitLayout(editor, mPreviewMeshPanel,
propertiesPanel));
}
public void destroy() {
mPreviewMeshPanel.destroy();
}
}

View File

@@ -1,89 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui
import java.awt.Graphics2D
import java.awt.geom.Point2D
import java.awt.geom.Rectangle2D
abstract class View {
/**
* x, y, width, and height are specified in this View's parent's coordinate system.
*/
open var x: Float = 0.0f
open var y: Float = 0.0f
open var width: Float = 0.0f
open var height: Float = 0.0f
/**
* True, if the View should participate in calls to findViewAt.
*/
open val reactToMouseEvents: Boolean = true
/**
* Bounds of the View with respect to its parent's coordinate system.
*/
val bounds: Rectangle2D.Float
get() = Rectangle2D.Float(x, y, width, height)
open val children: List<View> = emptyList()
open fun layout() { }
open fun render(g2d: Graphics2D) { }
}
fun layoutHierarchy(root: View) {
root.layout()
root.children.forEach { child -> layoutHierarchy(child) }
}
fun renderHierarchy(root: View, g2d: Graphics2D) {
g2d.translate(root.x.toDouble(), root.y.toDouble())
root.render(g2d)
root.children.forEach { child -> renderHierarchy(child, g2d) }
g2d.translate(-root.x.toDouble(), -root.y.toDouble())
}
/**
* Finds the deepest nested view that contains point p. The point p should be in root's coordinate
* system.
* This assumes views at the same level of the view hierarchy do not overlap each other.
*/
fun findViewAt(root: View, p: Point2D): View? {
val child = root.children.firstOrNull { v ->
v.bounds.contains(p)
} ?: return null
p.setLocation(p.x - child.x, p.y - child.y)
return findViewAt(child, p) ?: (if (child.reactToMouseEvents) child else null)
}
/**
* Converts from source's coordinates to destination's coordinates.
* source view must be a descendant of destination view.
*/
fun convertPoint(source: View, point: Point2D, destination: View): Point2D? {
if (source === destination) return point
for (child in destination.children) {
val result = convertPoint(source, Point2D.Float(point.x.toFloat() + child.x,
point.y.toFloat() + child.y), child)
if (result != null) return result
}
return null
}

View File

@@ -1,76 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview
import com.curiouscreature.kotlin.math.Float2
import com.curiouscreature.kotlin.math.Float3
import com.curiouscreature.kotlin.math.Float4
import com.curiouscreature.kotlin.math.length
import com.curiouscreature.kotlin.math.normalize
import com.curiouscreature.kotlin.math.rotation
import com.curiouscreature.kotlin.math.translation
import com.curiouscreature.kotlin.math.transpose
import com.google.android.filament.Camera
import kotlin.math.exp
private const val START_RADIUS = 3.0f
private fun rotateVector(rx: Float, ry: Float, v: Float3): Float3 {
val matrix = rotation(Float3(rx, ry, 0.0f))
return matrix.times(Float4(v)).xyz
}
internal class CameraManipulator(private val camera: Camera) {
private var cameraTranslation = Float3(z = START_RADIUS)
private var cameraRotation = Float3()
private var centerOfInterest = -START_RADIUS
fun updateCameraTransform() {
val view = translation(cameraTranslation) * rotation(cameraRotation)
camera.setModelMatrix(transpose(view).toFloatArray())
}
fun dolly(delta: Float, dollySpeed: Float) {
val eye = cameraTranslation
val v = rotateVector(cameraRotation.x, cameraRotation.y,
Float3(0.0f, 0.0f, centerOfInterest))
val view = eye + v
normalize(v)
val dollyBy = (1.0f - exp(-dollySpeed * delta)) * centerOfInterest
val newEye = eye + (v * dollyBy)
cameraTranslation = newEye
centerOfInterest = -length(newEye - view)
}
fun rotate(delta: Float2, rotateSpeed: Float) {
var rotX = cameraRotation.x
var rotY = cameraRotation.y
val eye = cameraTranslation
val view = eye + rotateVector(rotX, rotY, Float3(0.0f, 0.0f, centerOfInterest))
rotY += -delta.x * rotateSpeed
rotX += -delta.y * rotateSpeed
cameraTranslation = view - rotateVector(rotX, rotY, Float3(0.0f, 0.0f, centerOfInterest))
cameraRotation = Float3(rotX, rotY, cameraRotation.z)
}
}

View File

@@ -1,142 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview
import com.google.android.filament.Engine
import com.google.android.filament.Skybox
import com.google.android.filament.Texture
import com.google.android.filament.tungsten.texture.TextureCache
import com.google.android.filament.tungsten.texture.TextureUtils
import java.io.InputStream
import java.lang.RuntimeException
import java.nio.ByteBuffer
import kotlin.math.log2
private const val float = """([0-9-.]+)"""
private val pattern = Regex("""\(\s*$float\s*,\s*$float\s*,\s*$float\s*\);""")
internal fun parseSphereHarmonics(harmonics: String): FloatArray {
return harmonics.lines().fold(FloatArray(0)) { acc, line ->
val match = pattern.find(line)
val floats = match?.groups?.mapNotNull { group ->
group?.value?.toFloatOrNull()
}
floats?.toFloatArray()?.let {
acc + it
} ?: acc
}
}
internal class Ibl(val engine: Engine, private val pathPrefix: String) {
val environmentMap: Texture
val skyboxTexture: Texture
val skybox: Skybox
val irradiance: FloatArray
init {
environmentMap = loadCubemapLevel(null, 0, "m0_")
TextureCache.addTextureForRemoval(environmentMap)
for (i in 1 until environmentMap.levels) {
println("Loading level $i")
loadCubemapLevel(environmentMap, i, "m${i}_")
}
// Use non-prefixed images as skybox textures.
skyboxTexture = loadCubemapLevel(null, 0, "")
TextureCache.addTextureForRemoval(skyboxTexture)
skybox = loadSkybox()
irradiance = loadSphereHarmonics()
}
private fun loadSkybox(): Skybox {
return Skybox.Builder()
.environment(skyboxTexture)
.showSun(true)
.build(engine)
}
private fun loadSphereHarmonics(): FloatArray {
val path = "$pathPrefix/sh.txt"
val stream: InputStream = javaClass.classLoader.getResourceAsStream(path)
?: throw RuntimeException("Could not get stream for sphere harmonics at $path.")
val contents = stream.bufferedReader().use { it.readText() }
return parseSphereHarmonics(contents)
}
private fun loadCubemapLevel(texture: Texture?, level: Int, facePrefix: String): Texture {
require(texture != null || level == 0)
val cubemapFaces = listOf("px", "nx", "py", "ny", "pz", "nz")
val faceOffsets = IntArray(6)
val rawBuffers = cubemapFaces.map { face ->
val path = "$pathPrefix/$facePrefix$face.rgb32f"
val stream: InputStream = javaClass.classLoader.getResourceAsStream(path)
?: throw RuntimeException("Could not get stream for cubemap face $path.")
val bufferAndInfo = TextureUtils.loadImageBufferFromStream(stream)
?: throw RuntimeException("Could not load cubemap face $path.")
val (_, info) = bufferAndInfo
if (info.width != info.height) {
throw RuntimeException("Cubemap face $pathPrefix width != height")
}
bufferAndInfo
}
val firstFace = rawBuffers[0]
val (_, firstFaceInfo) = firstFace
val size = firstFaceInfo.width
// Assuming that size is a PO2
val levels = if (facePrefix != "") {
(log2(size.toFloat()) + 1).toInt()
} else {
1
}
// Allocate a byte buffer large enough to hold all the faces
val buffer = ByteBuffer.allocate(size * size * 4 * 6)
for ((rawBuffer, _) in rawBuffers) {
buffer.put(rawBuffer)
}
buffer.position(0)
val bufferDescriptor =
Texture.PixelBufferDescriptor(buffer, Texture.Format.RGB, Texture.Type.UINT_10F_11F_11F_REV)
// If the texture hasn't been created yet, create it.
val resultTexture = texture ?: Texture.Builder()
.width(size)
.height(size)
.levels(levels)
.format(Texture.InternalFormat.R11F_G11F_B10F)
.sampler(Texture.Sampler.SAMPLER_CUBEMAP)
.build(engine)
for (i in 0..5) {
faceOffsets[i] = size * size * 4 * i
}
resultTexture.setImage(engine, level, bufferDescriptor, faceOffsets)
return resultTexture
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview;
import com.google.android.filament.Engine;
import com.google.android.filament.IndirectLight;
import com.google.android.filament.Scene;
public final class LightHelpers {
private LightHelpers() { }
static IndirectLight addIndirectLight(Engine engine, Scene scene) {
Ibl i = new Ibl(engine, "ibls/venetian_crossroads_2k");
IndirectLight ibl =
new IndirectLight.Builder()
.irradiance(3, i.getIrradiance())
.reflections(i.getEnvironmentMap())
.intensity(30000.0f)
.build(engine);
scene.setIndirectLight(ibl);
scene.setSkybox(i.getSkybox());
return ibl;
}
}

View File

@@ -1,43 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview;
import com.google.android.filament.Camera;
class PreviewCamera {
private static final double FOV = 65.0;
private static final double NEAR_PLANE = 0.1;
private static final double FAR_PLANE = 200.0;
private final Camera mCamera;
public PreviewCamera(Camera camera) {
mCamera = camera;
}
public void setProjection(int width, int height) {
float displayRatio = (float) width / (float) height;
Camera.Fov axis;
if (width < height) {
axis = Camera.Fov.VERTICAL;
} else {
axis = Camera.Fov.HORIZONTAL;
}
mCamera.setProjection(FOV, displayRatio, NEAR_PLANE, FAR_PLANE, axis);
}
}

View File

@@ -1,183 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview;
import com.google.android.filament.Box;
import com.google.android.filament.Camera;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.FilamentPanel;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.IndirectLight;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.RenderableManager;
import com.google.android.filament.Renderer;
import com.google.android.filament.Scene;
import com.google.android.filament.VertexBuffer;
import com.google.android.filament.View;
import com.google.android.filament.Viewport;
import com.google.android.filament.filamesh.Filamesh;
import com.google.android.filament.filamesh.FilameshLoader;
import com.google.android.filament.tungsten.Filament;
import com.google.android.filament.tungsten.MathUtils;
import java.awt.BorderLayout;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PreviewMeshPanel extends JPanel {
// Filament resources
private Renderer mRenderer;
private Scene mScene;
private Camera mCamera;
private View mView;
private FilamentPanel mFilamentPanel;
private VertexBuffer mVertexBuffer;
private IndexBuffer mIndexBuffer;
private IndirectLight mIndirectLight;
private int mMeshRenderable;
private int mMeshEntity;
private int mMeshTransform;
private int mSun;
private int mPointLight;
private PreviewCamera mPreviewCamera;
private Filament.Viewer mViewer;
public PreviewMeshPanel() {
// On Windows D3D seems to mess with our OpenGLContext, this disable it.
System.setProperty("sun.java2d.d3d", "false");
// Avoid flickering on X11 (Linux).
System.setProperty("sun.awt.noerasebackground", "true");
System.setProperty("sun.awt.erasebackgroundonresize", "false");
setLayout(new BorderLayout());
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
mScene = engine.createScene();
mCamera = engine.createCamera(EntityManager.get().create());
mView = engine.createView();
mRenderer = engine.createRenderer();
mPreviewCamera = new PreviewCamera(mCamera);
// Load the vertex and index buffers
ClassLoader classLoader = getClass().getClassLoader();
InputStream previewMesh = classLoader.getResourceAsStream("sphere.mesh");
Filamesh mesh = FilameshLoader.load("mesh", previewMesh, engine);
mVertexBuffer = mesh.getVertexBuffer();
mIndexBuffer = mesh.getIndexBuffer();
mCamera.setProjection(90.0, 1.3, 0.1, 200.0, Camera.Fov.HORIZONTAL);
mCamera.lookAt(1.5f, 1.5f, 1.5f, 0, 0, 0, 0, 1, 0);
mIndirectLight = LightHelpers.addIndirectLight(engine, mScene);
loadMesh(engine, mScene, mVertexBuffer, mIndexBuffer);
int deviceScaleFactor = getDeviceScaleFactor();
mView.setViewport(new Viewport(0, 0, 640 * deviceScaleFactor,
480 * deviceScaleFactor));
mView.setShadowsEnabled(false);
mView.setCamera(mCamera);
mView.setScene(mScene);
mView.setClearColor(0f, 0f, 0f, 1.0f);
mFilamentPanel = new FilamentPanel();
mViewer = new TungstenViewer(mCamera, PreviewMeshPanel.this);
assert mFilamentPanel != null;
assert mView != null;
assert mRenderer != null;
mViewer.panel = mFilamentPanel;
mViewer.view = mView;
mViewer.renderer = mRenderer;
Filament.getInstance().addViewer(mViewer);
SwingUtilities.invokeLater(() -> {
add(mFilamentPanel, BorderLayout.CENTER);
revalidate();
});
});
}
public void updateMaterial(MaterialInstance newMaterialInstance) {
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
mMeshRenderable = engine.getRenderableManager().getInstance(mMeshEntity);
engine.getRenderableManager().setMaterialInstanceAt(mMeshRenderable, 0,
newMaterialInstance);
});
}
public void destroy() {
Filament.getInstance().removeViewer(mViewer);
Filament.getInstance().runOnFilamentThread((Engine engine) -> {
engine.destroyRenderer(mRenderer);
engine.destroyScene(mScene);
engine.destroyCameraComponent(mCamera.getEntity());
EntityManager.get().destroy(mCamera.getEntity());
engine.destroyView(mView);
mFilamentPanel.destroy(engine);
engine.destroyVertexBuffer(mVertexBuffer);
engine.destroyIndexBuffer(mIndexBuffer);
engine.destroyIndirectLight(mIndirectLight);
EntityManager.get().destroy(mMeshEntity);
engine.getRenderableManager().destroy(mMeshEntity);
engine.getTransformManager().destroy(mMeshTransform);
});
}
private void loadMesh(Engine engine, Scene scene, VertexBuffer vb, IndexBuffer ib) {
mMeshEntity = EntityManager.get().create();
mMeshTransform = engine.getTransformManager().create(mMeshEntity, 0,
MathUtils.createUniformScaleMatrix(3.0f));
RenderableManager.Builder renderableBuilder = new RenderableManager.Builder(1);
renderableBuilder
.geometry(0, RenderableManager.PrimitiveType.TRIANGLES, vb, ib)
.boundingBox(new Box(0, 0, 0, 1f, 1f, 1f))
.build(engine, mMeshEntity);
scene.addEntity(mMeshEntity);
}
private static int getDeviceScaleFactor() {
final GraphicsDevice defaultScreenDevice = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
int scaleFactor = 1;
final Class<?> screenDeviceClass = defaultScreenDevice.getClass();
if (screenDeviceClass.getCanonicalName().equals("sun.awt.CGraphicsDevice")) {
try {
final Method getScaleFactorMethod =
screenDeviceClass.getDeclaredMethod("getScaleFactor");
Object result = getScaleFactorMethod.invoke(defaultScreenDevice);
return (int) result;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
System.err.println("Unable to determine scale factor of screen, defaulting to 1");
}
}
return scaleFactor;
}
}

View File

@@ -1,89 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview
import com.curiouscreature.kotlin.math.Float2
import com.google.android.filament.Camera
import com.google.android.filament.Viewport
import com.google.android.filament.tungsten.Filament
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
private const val SPEED_MULTIPLIER = 100.0f
private const val DOLLY_MULTIPLIER = 5.0f
private fun MouseEvent.toFloat2() = Float2(this.x.toFloat(), this.y.toFloat())
internal class TungstenViewer(camera: Camera, val previewMeshPanel: PreviewMeshPanel)
: Filament.Viewer() {
private val previewCamera = PreviewCamera(camera)
private val manipulator = CameraManipulator(camera)
private var lastMouse: Float2? = null
init {
previewMeshPanel.addMouseMotionListener(object : MouseMotionListener {
override fun mouseMoved(e: MouseEvent?) { }
override fun mouseDragged(e: MouseEvent?) {
e ?: return
lastMouse?.let {
val delta = e.toFloat2() - it
val scaledDelta = delta / Float2(previewMeshPanel.width.toFloat(),
previewMeshPanel.height.toFloat())
manipulator.rotate(scaledDelta, SPEED_MULTIPLIER)
}
lastMouse = e.toFloat2()
}
})
previewMeshPanel.addMouseListener(object : MouseListener {
override fun mouseReleased(e: MouseEvent?) { }
override fun mouseEntered(e: MouseEvent?) { }
override fun mouseClicked(e: MouseEvent?) { }
override fun mouseExited(e: MouseEvent?) { }
override fun mousePressed(e: MouseEvent?) {
e ?: return
lastMouse = e.toFloat2()
}
})
previewMeshPanel.addMouseWheelListener { e ->
e?.let { mouseWheelEvent ->
// Swing reports wheel events caused by a physical wheel (opposed to a trackpad)
// with an inverted sign. Flip it so it feels more intuitive.
val causedByWheel = mouseWheelEvent.wheelRotation != 0
val multiplier = if (causedByWheel) 1 else -1
manipulator.dolly(multiplier * mouseWheelEvent.preciseWheelRotation.toFloat() /
previewMeshPanel.width, DOLLY_MULTIPLIER)
}
}
}
override fun update(deltaTimeMs: Long) {
manipulator.updateCameraTransform()
val width = previewMeshPanel.width
val height = previewMeshPanel.height
view.setViewport(Viewport(0, 0, width, height))
previewCamera.setProjection(width, height)
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.util;
public final class OperatingSystem {
private static final String OS_PROPERTY =
System.getProperties().getProperty("os.name", "generic").toLowerCase();
private OperatingSystem() {
}
public static boolean isWindows() {
return OS_PROPERTY.contains("win");
}
public static boolean isMac() {
return OS_PROPERTY.contains("mac") || OS_PROPERTY.contains("darwin");
}
public static boolean isLinux() {
return OS_PROPERTY.contains("nux");
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
import org.junit.Assert.assertEquals
import org.junit.Test
class ExpressionTest {
@Test
fun `toString returns the symbol`() {
assertEquals("float3(1.0, 2.0, 3.0)", Expression("float3(1.0, 2.0, 3.0)", 3).toString())
}
@Test
fun `swizzle to a lower dimension`() {
assertEquals("exp.rg", "${Expression("exp", 4).rg}")
}
@Test
fun `swizzle to a higher dimension`() {
assertEquals("float4(exp, 0.0, 0.0)", "${Expression("exp", 2).rgba}")
}
@Test
fun `swizzling a literal returns a new literal`() {
assertEquals("float2(0.0, 0.0)", "${Literal(4).rg}")
assertEquals("float3(0.0, 0.0, 0.0)", "${Literal(1).rgb}")
}
@Test
fun `create a float literal of dimension 1`() {
assertEquals("float(0.0)", "${Literal(1).r}")
}
}

View File

@@ -1,73 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.compiler
import com.google.android.filament.tungsten.model.Graph
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.createAddNode
import com.google.android.filament.tungsten.model.createFloat3ConstantNode
import com.google.android.filament.tungsten.model.createShaderNode
import org.junit.Assert.assertNotEquals
import org.junit.Test
class GraphCompilerTest {
/**
* For now, we just check that the graphs can compile without throwing any errors.
* TODO: add more robust checks
*/
@Test
fun `Compile single node`() {
val adderNode = createAddNode(0)
val graph = Graph(nodes = listOf(adderNode), rootNodeId = adderNode.id)
val compiler = GraphCompiler(graph)
compiler.compileGraph()
}
@Test
fun `Compile two nodes with connections`() {
val constantNodeA = createFloat3ConstantNode(0)
val constantNodeB = createFloat3ConstantNode(1)
val adderNode = createAddNode(2)
val shaderNode = createShaderNode(3)
val graph = Graph(
nodes = listOf(shaderNode, adderNode, constantNodeA, constantNodeB),
rootNodeId = shaderNode.id,
connections = listOf(
adderNode.getOutputSlot("out") to shaderNode.getInputSlot("baseColor"),
constantNodeA.getOutputSlot("result") to adderNode.getInputSlot("a"),
constantNodeB.getOutputSlot("result") to adderNode.getInputSlot("b")
)
)
val compiler = GraphCompiler(graph)
compiler.compileGraph()
}
@Test
fun `multiple material parameters have unique names`() {
val compiler = GraphCompiler(createMockGraph())
val first = compiler.addParameter("float3", "parameter")
val second = compiler.addParameter("float3", "parameter")
assertNotEquals(first, second)
}
private fun createMockGraph() = Graph(
nodes = listOf(Node(id = 0, type = "node")),
rootNodeId = 0)
}

View File

@@ -1,59 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model
import com.google.android.filament.tungsten.compiler.Literal
import org.junit.Assert.assertEquals
import org.junit.Test
class NodeTest {
@Test
fun `resolveInputExpressions keeps two float4s the same length`() {
val (a, b) = resolveInputExpressions(Literal(4), Literal(4))
assertEquals(4, a.dimensions)
assertEquals(4, b.dimensions)
}
@Test
fun `resolveInputExpressions keeps two floats the same length`() {
val (a, b) = resolveInputExpressions(Literal(1), Literal(1))
assertEquals(1, a.dimensions)
assertEquals(1, b.dimensions)
}
@Test
fun `resolveInputExpressions trims both a float3 and a float2 to a float2`() {
val (a, b) = resolveInputExpressions(Literal(3), Literal(2))
assertEquals(2, a.dimensions)
assertEquals(2, b.dimensions)
}
@Test
fun `resolveInputExpressions allows a float as input for scalar operations`() {
val (a, b) = resolveInputExpressions(Literal(3), Literal(1))
assertEquals(3, a.dimensions)
assertEquals(1, b.dimensions)
}
@Test
fun `resolveInputExpressions defaults to float4 when a single input is not present`() {
val (a, b) = resolveInputExpressions(null, Literal(1))
assertEquals(4, a.dimensions)
assertEquals(1, b.dimensions)
}
}

View File

@@ -1,97 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
import org.junit.Assert.assertEquals
import org.junit.Test
class GraphFileTest {
@Test
fun `Extract missing tool block`() {
assertEquals("", GraphFile.extractToolBlockFromMaterialFile("""
|material {
| ...
|}
|
|fragment {
| ...
|}
""".trimMargin()))
}
@Test
fun `Extract empty tool block`() {
assertEquals("", GraphFile.extractToolBlockFromMaterialFile("""
|material {
| ...
|}
|
|fragment {
| ...
|}
|
|tool {}
""".trimMargin()))
}
@Test
fun `Extract tool block with extra whitespace`() {
assertEquals("\nfoobar\n", GraphFile.extractToolBlockFromMaterialFile("""
|material {
| ...
|}
|
|fragment {
| ...
|}
|
| tool {
|foobar
|}
""".trimMargin()))
}
@Test
fun `Extract tool block with JSON`() {
assertEquals("\n {\"foo\":\"bar\",\"fizz\":[\"buzz\", 1]}\n",
GraphFile.extractToolBlockFromMaterialFile("""
|material {
| ...
|}
|
|fragment {
| ...
|}
|
|tool {
| {"foo":"bar","fizz":["buzz", 1]}
|}
""".trimMargin()))
}
@Test
fun `Add tool block`() {
assertEquals("""
|content
|
|tool {
|tool content
|}
""".trimMargin(), GraphFile.addToolBlockToMaterialFile("content", "tool content"))
}
}

View File

@@ -1,130 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.model.serialization
import com.google.android.filament.tungsten.model.Float3
import com.google.android.filament.tungsten.model.Graph
import com.google.android.filament.tungsten.model.Node
import com.google.android.filament.tungsten.model.NodeId
import com.google.android.filament.tungsten.model.Property
import com.google.android.filament.tungsten.model.copyPropertyWithValue
import com.google.android.filament.tungsten.properties.ColorChooser
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* These test cases perform round-trip serialization. A Graph is serialized to a String, then
* deserialized back to a Graph. The result is verified to equal the original.
*/
class GraphSerializerTest {
private val mockNodeFactory = object : INodeFactory {
override fun createNodeForTypeIdentifier(typeIdentifier: String, id: NodeId): Node? {
return if (typeIdentifier == "node") createMockNode(id) else null
}
}
@Test
fun `Serialize empty model`() {
val graph = Graph()
val serialized = GraphSerializer.serialize(graph, emptyMap())
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
assertEquals(graph, deserialized)
}
@Test
fun `Serialize editor data`() {
val serialized = GraphSerializer.serialize(Graph(), mapOf("foo" to "bar"))
val (_, editorData) = GraphSerializer.deserialize(serialized, mockNodeFactory)
assertEquals(mapOf("foo" to "bar"), editorData)
}
@Test
fun `Serialize nodes and connections`() {
val first = createMockNode(0)
val second = first.copy(id = 1)
val root = first.copy(id = 2)
val graph = Graph(
nodes = listOf(first, second, root),
rootNodeId = root.id,
connections = listOf(
first.getOutputSlot("output") to second.getInputSlot("input"),
second.getOutputSlot("output") to root.getInputSlot("input")
)
)
val serialized = GraphSerializer.serialize(graph, emptyMap())
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
assertEquals(graph, deserialized)
}
/**
* If a node type identifier is not recognized, avoid adding the node or connection to the
* graph.
*/
@Test
fun `Serialize connection with unknown node type`() {
val first = createMockNode(0)
val root = createMockNode(1).copy(type = "unknown type")
val graph = Graph(
nodes = listOf(first, root),
rootNodeId = root.id,
connections = listOf(first.getOutputSlot("output") to root.getInputSlot("input")))
val serialized = GraphSerializer.serialize(graph, emptyMap())
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
assertEquals(1, deserialized.nodes.size)
assertEquals(graph.nodes[0], deserialized.nodes[0])
assertEquals(0, deserialized.connections.size)
}
@Test
fun `Serialize a property`() {
val node = createMockNode(0).copy()
// Create a graph with a non-default property value
val graph = Graph(
nodes = listOf(node),
rootNodeId = 0
).graphByChangingProperty(node.getPropertyHandle("mockProperty"),
copyPropertyWithValue(mockProperty, Float3(1.0f, 2.0f, 3.0f)))
val serialized = GraphSerializer.serialize(graph, emptyMap())
val (deserialized, _) = GraphSerializer.deserialize(serialized, mockNodeFactory)
assertEquals(graph, deserialized)
}
private val mockProperty = Property(
name = "mockProperty",
value = Float3(),
editorFactory = ::ColorChooser
)
private fun createMockNode(id: NodeId) = Node(
id = id,
type = "node",
inputSlots = listOf("input"),
outputSlots = listOf("output"),
properties = listOf(mockProperty)
)
}

View File

@@ -1,40 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.ui.preview
import org.junit.Assert.assertArrayEquals
import org.junit.Test
class IblTest {
@Test
fun `parseSphereHarmonics parses single row`() {
val result = parseSphereHarmonics(
"(0.001, -0.003, -0.005); // L20, irradiance, pre-scaled base\n")
val expected = floatArrayOf(0.001f, -0.003f, -0.005f)
assertArrayEquals(expected, result, 0.0f)
}
@Test
fun `parseSphereHarmonics parses multiple rows`() {
val result = parseSphereHarmonics(
"(-0.2, -0.24, -0.24); // L2-2, irradiance, pre-scaled base\n" +
"( 0.05, 0.06, 0.06); // L2-1, irradiance, pre-scaled base\n")
val expected = floatArrayOf(-0.2f, -0.24f, -0.24f, 0.05f, 0.06f, 0.06f)
assertArrayEquals(expected, result, 0.0f)
}
}

View File

@@ -1 +0,0 @@
filament_tools_dir=../../../dist

Binary file not shown.

View File

@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

172
tools/tungsten/gradlew vendored
View File

@@ -1,172 +0,0 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -1,84 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,73 +0,0 @@
import java.nio.file.Paths
buildscript {
repositories {
mavenCentral()
maven {
url 'https://plugins.gradle.org/m2/'
}
maven {
url 'http://dl.bintray.com/jetbrains/intellij-plugin-service'
}
}
}
plugins {
id 'org.jetbrains.intellij' version '0.3.5'
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
apply plugin: 'java'
// Bundle matc binary and Filament shared library
def filament_path = file("../../../dist").absolutePath
if (project.hasProperty("filament_dist_dir")) {
filament_path = file("$filament_dist_dir").absolutePath
}
// matc binary
def matc = ['/bin/matc.exe', '/bin/matc']
def matcFullPath = matc.collect { path -> Paths.get(filament_path, path) }
// Filament shared library
def library = ["/lib/x86_64/filament-jni.dll", "/lib/x86_64/libfilament-jni.dylib",
"/lib/x86_64/libfilament-jni.so"]
def libraryFullPath = library.collect { path -> Paths.get(filament_path, path) }
// Ensure that at least one matc binary and Filament library is present
if (!matcFullPath.any { path -> file(path).exists() }) {
throw new StopActionException("No matc binary could be found in " + filament_path +
"/bin. Ensure Filament has been built before building Tungsten.")
}
if (!libraryFullPath.any { path -> file(path).exists() }) {
throw new StopActionException("No Filament shared library could be found in " + filament_path +
"/lib/x86_64. Ensure Filament has been built before building Tungsten.")
}
processResources {
from( matcFullPath, libraryFullPath )
}
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
implementation project(':core')
}
intellij {
version '2017.1'
pluginName 'Tungsten'
updateSinceUntilBuild false
alternativeIdePath System.getenv('ANDROID_STUDIO')
dependencies {
runtime project(':core')
}
}
group 'com.google'
version '1.0.0'

View File

@@ -1,36 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import com.google.android.filament.tungsten.ui.GuiComponentFactory;
import com.intellij.ui.JBSplitter;
import javax.swing.JComponent;
public class IntelliJComponentFactory implements GuiComponentFactory {
@Override
public JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
JComponent third) {
JBSplitter verticalSplit = new JBSplitter(true);
verticalSplit.setFirstComponent(second);
verticalSplit.setSecondComponent(third);
JBSplitter horizontalSplit = new JBSplitter(false);
horizontalSplit.setFirstComponent(first);
horizontalSplit.setSecondComponent(verticalSplit);
return horizontalSplit;
}
}

View File

@@ -1,51 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin
import com.google.android.filament.tungsten.ui.TungstenFile
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.vfs.VirtualFile
import java.io.IOException
import java.util.function.BiConsumer
import java.util.function.Consumer
class IntelliJFile(val file: VirtualFile) : TungstenFile {
override fun write(contents: String, callback: Consumer<Boolean>) {
ApplicationManager.getApplication().runWriteAction {
try {
file.setBinaryContent(contents.toByteArray())
callback.accept(true)
} catch (e: IOException) {
e.printStackTrace()
callback.accept(false)
}
}
}
override fun read(result: BiConsumer<Boolean, String?>) {
ApplicationManager.getApplication().runReadAction {
try {
val contents = String(file.contentsToByteArray())
result.accept(true, contents)
} catch (e: IOException) {
e.printStackTrace()
result.accept(false, null)
}
}
}
}

View File

@@ -1,110 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import com.google.android.filament.tungsten.MaterialManager;
import com.google.android.filament.tungsten.ui.TungstenPanel;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorLocation;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VirtualFile;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MaterialFileEditor extends UserDataHolderBase implements FileEditor {
private final TungstenPanel mPanel;
public MaterialFileEditor(MaterialManager materialManager, VirtualFile file) {
mPanel = new TungstenPanel(new IntelliJComponentFactory(), materialManager,
new IntelliJFile(file));
}
@NotNull
@Override
public JComponent getComponent() {
return mPanel;
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return mPanel;
}
@NotNull
@Override
public String getName() {
return "Filament Material Editor";
}
@Override
public void setState(@NotNull FileEditorState state) {
}
@Override
public boolean isModified() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void selectNotify() {
}
@Override
public void deselectNotify() {
}
@Override
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Override
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Nullable
@Override
public BackgroundEditorHighlighter getBackgroundHighlighter() {
return null;
}
@Nullable
@Override
public FileEditorLocation getCurrentLocation() {
return null;
}
@Override
public void dispose() {
mPanel.destroy();
}
}

View File

@@ -1,97 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import com.google.android.filament.tungsten.Filament;
import com.google.android.filament.tungsten.MaterialManager;
import com.google.android.filament.tungsten.util.OperatingSystem;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorPolicy;
import com.intellij.openapi.fileEditor.FileEditorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
public class MaterialFileProvider implements ApplicationComponent, FileEditorProvider {
private static final String COMPONENT_NAME = "MaterialFileProvider";
private static final String EDITOR_TYPE_ID = "FilamentMaterial";
private String mMatcPath;
private MaterialManager mMaterialManager;
@Override
public void initComponent() {
try {
prepareFilament();
Filament.getInstance().start();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void disposeComponent() {
Filament.getInstance().shutdown();
}
@NotNull
@Override
public String getComponentName() {
return COMPONENT_NAME;
}
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
String materialFileExtension = MaterialFileType.getInstance().getDefaultExtension();
return materialFileExtension.equalsIgnoreCase(file.getExtension());
}
@NotNull
@Override
public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
return new MaterialFileEditor(mMaterialManager, file);
}
@NotNull
@Override
public String getEditorTypeId() {
return EDITOR_TYPE_ID;
}
@NotNull
@Override
public FileEditorPolicy getPolicy() {
return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
}
private void prepareFilament() throws IOException {
ResourceLoader.loadDynamicLibrary("filament-jni");
String matcFileName;
if (OperatingSystem.isWindows()) {
matcFileName = "matc.exe";
} else {
matcFileName = "matc";
}
File tempMatc = ResourceLoader.getResourceAsFile(matcFileName, "", true);
mMatcPath = tempMatc.getAbsolutePath();
mMaterialManager = new MaterialManager(mMatcPath);
}
}

View File

@@ -1,80 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import javax.swing.Icon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MaterialFileType implements FileType {
private static final String EXTENSION = "mat";
private static final String NAME = "Filament Material";
private static final String DESCRIPTION = "A material for the Filament rendering engine";
private static MaterialFileType instance;
static MaterialFileType getInstance() {
if (instance == null) {
instance = new MaterialFileType();
}
return instance;
}
@NotNull
@Override
public String getName() {
return NAME;
}
@NotNull
@Override
public String getDescription() {
return DESCRIPTION;
}
@NotNull
@Override
public String getDefaultExtension() {
return EXTENSION;
}
@Nullable
@Override
public Icon getIcon() {
return null;
}
@Override
public boolean isBinary() {
return false;
}
@Override
public boolean isReadOnly() {
return false;
}
@Nullable
@Override
public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) {
return CharsetToolkit.UTF8;
}
}

View File

@@ -1,30 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
public class MaterialFileTypeLoader extends FileTypeFactory {
@Override
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
consumer.consume(MaterialFileType.getInstance(),
MaterialFileType.getInstance().getDefaultExtension());
}
}

View File

@@ -1,96 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten.plugin;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Helpers to load resources packaged inside of a jar files.
*/
class ResourceLoader {
/**
* Load a dynamic library from a resource residing on the classpath.
* @param libName The portable library name, without any prefixes or extensions (i.e., library)
* This will automatically be converted to a platform-specific name, like
* liblibrary.dylib.
*/
static void loadDynamicLibrary(@Nonnull String libName) throws IOException {
String filePath = "/" + System.mapLibraryName(libName);
InputStream input = ResourceLoader.class.getResourceAsStream(filePath);
if (input == null) {
throw new FileNotFoundException("Could not find library " + filePath);
}
File file = createTemporaryFile(libName, null);
writeStreamToFile(input, file);
System.load(file.getAbsolutePath());
}
/**
* Get a File object from a resource residing on the classpath.
*/
@NotNull
public static File getResourceAsFile(@Nonnull String name, @Nonnull String extension,
boolean executable) throws IOException {
String filePath = "/" + name + extension;
InputStream input = ResourceLoader.class.getResourceAsStream(filePath);
if (input == null) {
throw new FileNotFoundException("Could not find resource file " + filePath);
}
File file = createTemporaryFile(name, extension);
if (!file.setExecutable(executable)) {
throw new RuntimeException("Could not make file executable " + filePath);
}
writeStreamToFile(input, file);
return file;
}
@NotNull
private static File createTemporaryFile(@Nonnull String name, @Nullable String extension)
throws IOException {
File file = File.createTempFile(name, extension);
file.deleteOnExit();
return file;
}
private static void writeStreamToFile(@Nonnull InputStream input, @Nonnull File file)
throws IOException {
OutputStream out = new FileOutputStream(file);
int read;
byte[] bytes = new byte[4096];
while ((read = input.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
out.flush();
out.close();
}
}

View File

@@ -1,26 +0,0 @@
<idea-plugin>
<id>com.example.plugin</id>
<name>Tungsten</name>
<vendor email="filament@google.com" url="http://google.com">Google</vendor>
<description><![CDATA[
Some description.
]]></description>
<change-notes><![CDATA[
Release notes here.
]]>
</change-notes>
<depends>com.intellij.modules.lang</depends>
<application-components>
<component>
<implementation-class>com.google.android.filament.tungsten.plugin.MaterialFileProvider</implementation-class>
</component>
</application-components>
<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.google.android.filament.tungsten.plugin.MaterialFileTypeLoader" />
</extensions>
</idea-plugin>

View File

@@ -1,5 +0,0 @@
include 'core', 'standalone', 'plugin', 'kotlin-math'
project(':kotlin-math').projectDir = new File('third_party/kotlin-math')
rootProject.name = 'tungsten'

View File

@@ -1,28 +0,0 @@
plugins {
id 'java'
id 'application'
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
}
mainClassName = 'com.google.android.filament.tungsten.Tungsten'
sourceSets {
main {
java {
srcDirs = ['src']
}
}
}
run {
systemProperty 'java.library.path', '../../../dist/lib/x86_64'
}
dependencies {
implementation project(':core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.51"
}
repositories {
jcenter()
}

View File

@@ -1,62 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten
import com.google.android.filament.tungsten.ui.TungstenFile
import java.io.File
import java.io.IOException
import java.util.function.BiConsumer
import java.util.function.Consumer
class StandaloneFile(private val filePath: String) : TungstenFile {
override fun write(contents: String, callback: Consumer<Boolean>) {
try {
File(filePath).writeText(contents)
callback.accept(true)
} catch (e: IOException) {
e.printStackTrace()
callback.accept(false)
}
}
override fun read(result: BiConsumer<Boolean, String?>) {
try {
result.accept(true, File(filePath).readText())
} catch (e: IOException) {
e.printStackTrace()
result.accept(false, null)
}
}
}
/**
* An InMemoryFile represents a file that has not been written to disk yet.
* When the user opens the standalone version of Tungsten, they are editing this in-memory buffer.
* When a file is first chosen to save to, the InMemoryFile can be "upgraded" to a StandaloneFile.
*/
class InMemoryFile(private var buffer: String = "") : TungstenFile {
override fun write(contents: String, callback: Consumer<Boolean>) {
buffer = contents
callback.accept(true)
}
override fun read(result: BiConsumer<Boolean, String?>) {
result.accept(true, buffer)
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import com.google.android.filament.tungsten.ui.GuiComponentFactory;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
public class SwingComponentFactory implements GuiComponentFactory {
@Override
public JComponent createThreeComponentSplitLayout(JComponent first, JComponent second,
JComponent third) {
JSplitPane verticalSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, second, third);
JSplitPane horizontalSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, first,
verticalSplit);
verticalSplit.setContinuousLayout(true);
horizontalSplit.setContinuousLayout(true);
return horizontalSplit;
}
}

View File

@@ -1,73 +0,0 @@
/*
* 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.
*/
package com.google.android.filament.tungsten;
import com.google.android.filament.tungsten.ui.TungstenPanel;
import com.google.android.filament.tungsten.util.OperatingSystem;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class Tungsten {
public static void main(String[] args) {
String matcPath;
if (OperatingSystem.isWindows()) {
matcPath = "../../../dist/bin/matc.exe";
} else {
matcPath = "../../../dist/bin/matc";
}
MaterialManager materialManager = new MaterialManager(matcPath);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
System.loadLibrary("filament-jni");
Filament.getInstance().start();
TungstenPanel panel = createTungstenFrameAndPanel();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
panel.destroy();
Filament.getInstance().shutdown();
}));
}
private TungstenPanel createTungstenFrameAndPanel() {
JFrame tungstenFrame = new JFrame();
TungstenPanel tungstenPanel = new TungstenPanel(new SwingComponentFactory(),
materialManager, new InMemoryFile());
tungstenFrame.setTitle("Tungsten");
tungstenFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
tungstenFrame.add(tungstenPanel);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
tungstenFrame.setSize(screenSize);
tungstenFrame.setLocationRelativeTo(null);
tungstenFrame.setVisible(true);
return tungstenPanel;
}
});
}
}

View File

@@ -1,22 +0,0 @@
.DS_Store
.idea/shelf
/dependencies
/dist
out/
workspace.xml
*.versionsBackup
.gradle/
build/
!**/src/**/build
!**/test/**/build
*.iml
.idea/libraries/
.idea/modules
.idea/modules.xml
.idea/gradle.xml
.idea/compiler.xml
.idea/inspectionProfiles/profiles_settings.xml
.idea/runConfigurations.xml
.idea/.name
local.properties
*.ser

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -1,176 +0,0 @@
# kotlin-math
Set of Kotlin APIs to make graphics math easier to write. These APIs are mostly
modeled after GLSL (OpenGL Shading Language) to make porting code to and from
shaders easier.
The various types offered by this library are only meant to be _value types_.
Most APIs are therefore exposed as top-level functions and not as methods.
For instance:
```kotlin
val v = Float3(1.0f, 3.0f, 4.0f)
val n = normalize(v)
```
## Building the project
Simply run the following command to generate `build/libs/kotlin-math.jar`:
```bash
$ ./gradlew assemble
```
## Types
Vector types:
- Float2, vector of 2 floats
- Float3, vector of 3 floats
- Float4, vector of 4 floats
- Bool2, vector of 2 booleans
- Bool3, vector of 3 booleans
- Bool4, vector of 4 booleans
Matrix types:
- Mat3, 3x3 float matrix
- Mat4, 4x4 float matrix
## Vector types
### Accessing components
Each vector type exposes its component as properties:
```kotlin
val x = myVector.x
val (x, y, z) = myVector
```
A vector can also be treated as an array:
```kotlin
val x = myVector[0]
val x = myVector[VectorComponents.X]
```
The traditional mathematical form with 1-based indexing can be used:
```kotlin
val x = myVector(1)
```
### Property aliases
To improve code readability, the vector types provide aliases for each property,
allowing you to choose the most appropriate names:
```kotlin
val (x, y, z) = myPosition
val (r, g, b) = myColor
val (s, t) = myTextureCoordinates
```
### Swizzling
Vector types also provide different ways to swizzle their components, although
in a more limited way than in GLSL. The most obvious use for swizzling is to
extract sub-vectors:
```kotlin
val position = Float3()
val position2d = position.xy // extract a Float2
val colorWithAlpha = Float4()
val rgbColor = colorWithAlpha.rgb // extract a Float3
```
The get operators allows for more complex swizzling by enabling re-ordering and
duplication of the components:
```kotlin
val colorWithAlpha = Float4()
val bgrColor = colorWithAlpha[
VectorComponents.B,
VectorComponents.G,
VectorComponents.R
] // re-ordered 3 components sub-vector
```
### Comparing vector types
Vector comparisons follow GLSL rules:
- `==` returns true if all components are equal
- `!=` returns true if not all components are equal
In addition you can use component-wise relational operators that return a vector
of boolean with the result of each component-wise comparison:
- `lessThan`
- `lessThanEqual`
- `greaterThan`
- `greaterThanEqual`
- `equal`
- `notEqual`
Example:
```kotlin
if (all(lessThan(v1, v2))) {
// …
}
```
You can also use the following infix operators if you prefer the operator
syntax:
- `lt`
- `lte`
- `gt`
- `gte`
- `eq`
- `neq`
Example:
```kotlin
if (any(v1 lte v2)) {
// …
}
```
## Matrix types
Matrices are represented as a set of column vectors. For instance, a `Mat4` can
be destructured into the right, up, forward and translation vectors:
```kotlin
val (right, up, forward, translation) = myMat4
```
Each vector can be accessed as a property or by its index:
```kotlin
forward = myMat4.forward
forward = myMat4.z
forward = myMat4[2]
forward = myMat4[MatrixColumns.Z]
```
Matrix types also offer APIs to access each element individually by specifying
the column then row:
```kotlin
v = myMat4.z[1]
v = myMat4[2, 1]
v = myMat4[MatrixColumns.Z, 1]
```
You can also use the invoke operator to access elements in row-major mode with
1-based indices to follow the traditional mathematical notation:
```kotlin
v = myMat4(2, 3) // equivalent to myMat4[2, 1]
```
## Scalar APIs
The file `Scalar.kt` contains various helper methods to use common math operations
with floats. It is intended to be used in combination with Kotlin 1.2's new float
math methods.

View File

@@ -1,20 +0,0 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.2.10"
}
apply plugin: 'idea'
apply plugin: 'kotlin'
repositories {
jcenter()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
testImplementation 'junit:junit:4.12'
}
sourceSets {
main.kotlin.srcDirs += 'src/main/kotlin'
}

Some files were not shown because too many files have changed in this diff Show More