diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index be96ccc..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-dist: trusty
-
-language: cpp
-
-os: linux
-
-compiler:
- - gcc
- - clang
-
-env:
- - BUILD_CONFIGURATION=debug
- - BUILD_CONFIGURATION=release
-
-install:
- - source build/travis.sh
-
-script:
- - cd build/linux
- - make BUILD=$BUILD_CONFIGURATION
diff --git a/README.md b/README.md
index 71ca0a5..d5dfc5a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
A simple, cross-platform 2D game engine with OpenGL renderer. Supports Linux and
-Android (lolipop+) platforms.
+Android (lolipop+) platforms. My personal hobby project.
+I've published a game on [Google Play](https://play.google.com/store/apps/details?id=com.woom.game) based on the engine. The demo included in this repository is an early prototype of the game.
#### Building the demo
Linux:
```text
@@ -21,7 +22,6 @@ ninja -C out/release
[jsoncpp](https://github.com/open-source-parsers/jsoncpp),
[minimp3](https://github.com/lieff/minimp3),
[oboe](https://github.com/google/oboe),
-[r8brain-free-src](https://github.com/avaneev/r8brain-free-src),
[stb](https://github.com/nothings/stb),
[texture-compressor](https://github.com/auygun/kaliber/tree/master/src/third_party/texture_compressor),
[minizip](https://github.com/madler/zlib/tree/master/contrib/minizip)
diff --git a/assets/engine/pass_through.glsl_fragment b/assets/engine/pass_through.glsl_fragment
index 29f1730..620d3ad 100644
--- a/assets/engine/pass_through.glsl_fragment
+++ b/assets/engine/pass_through.glsl_fragment
@@ -3,10 +3,10 @@ precision mediump float;
#endif
uniform vec4 color;
-uniform sampler2D texture;
+uniform sampler2D texture_0;
varying vec2 tex_coord_0;
void main() {
- gl_FragColor = texture2D(texture, tex_coord_0) * color;
+ gl_FragColor = texture2D(texture_0, tex_coord_0) * color;
}
diff --git a/assets/engine/pass_through.glsl_vertex b/assets/engine/pass_through.glsl_vertex
index a9b3956..06bca35 100644
--- a/assets/engine/pass_through.glsl_vertex
+++ b/assets/engine/pass_through.glsl_vertex
@@ -3,7 +3,6 @@ attribute vec2 in_tex_coord_0;
uniform vec2 scale;
uniform vec2 offset;
-uniform vec2 pivot;
uniform vec2 rotation;
uniform vec2 tex_offset;
uniform vec2 tex_scale;
@@ -15,10 +14,9 @@ void main() {
// Simple 2d transform.
vec2 position = in_position;
position *= scale;
- position += pivot;
position = vec2(position.x * rotation.y + position.y * rotation.x,
position.y * rotation.y - position.x * rotation.x);
- position += offset - pivot;
+ position += offset;
tex_coord_0 = (in_tex_coord_0 + tex_offset) * tex_scale;
diff --git a/assets/engine/quad.mesh b/assets/engine/quad.mesh
deleted file mode 100644
index 53bb937..0000000
--- a/assets/engine/quad.mesh
+++ /dev/null
@@ -1,10 +0,0 @@
-// This creates a normalized unit sized quad.
-{
- "primitive": "TriangleStrip",
- "vertex_description": "p2f;t2f",
- "num_vertices": 4,
- "vertices": [-0.5, -0.5, 0.0, 1.0,
- 0.5, -0.5, 1.0, 1.0,
- -0.5, 0.5, 0.0, 0.0,
- 0.5, 0.5, 1.0, 0.0]
-}
diff --git a/assets/engine/solid.glsl_vertex b/assets/engine/solid.glsl_vertex
index f1ac061..5c939fd 100644
--- a/assets/engine/solid.glsl_vertex
+++ b/assets/engine/solid.glsl_vertex
@@ -3,7 +3,6 @@ attribute vec2 in_tex_coord_0;
uniform vec2 scale;
uniform vec2 offset;
-uniform vec2 pivot;
uniform vec2 rotation;
uniform mat4 projection;
@@ -11,10 +10,9 @@ void main() {
// Simple 2d transform.
vec2 position = in_position;
position *= scale;
- position += pivot;
position = vec2(position.x * rotation.y + position.y * rotation.x,
position.y * rotation.y - position.x * rotation.x);
- position += offset - pivot;
+ position += offset;
gl_Position = projection * vec4(position, 0.0, 1.0);
}
diff --git a/build/README.md b/build/README.md
deleted file mode 100644
index 88ac312..0000000
--- a/build/README.md
+++ /dev/null
@@ -1 +0,0 @@
-[![Build Status](https://travis-ci.org/auygun/kaliber.svg?branch=master)](https://travis-ci.org/auygun/kaliber)
diff --git a/build/android/app/CMakeLists.txt b/build/android/app/CMakeLists.txt
index 458d52a..6c58896 100644
--- a/build/android/app/CMakeLists.txt
+++ b/build/android/app/CMakeLists.txt
@@ -47,10 +47,11 @@ endif ()
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
-add_library(native-activity SHARED
+add_library(kaliber SHARED
../../../src/base/collusion_test.cc
../../../src/base/log.cc
../../../src/base/random.cc
+ ../../../src/base/sinc_resampler.cc
../../../src/base/task_runner.cc
../../../src/base/timer.cc
../../../src/base/vecmath.cc
@@ -73,16 +74,17 @@ add_library(native-activity SHARED
../../../src/engine/image_quad.cc
../../../src/engine/image.cc
../../../src/engine/mesh.cc
+ ../../../src/engine/persistent_data.cc
../../../src/engine/platform/asset_file_android.cc
../../../src/engine/platform/asset_file.cc
../../../src/engine/platform/platform_android.cc
../../../src/engine/platform/platform_base.cc
../../../src/engine/renderer/geometry.cc
- ../../../src/engine/renderer/render_command.cc
+ ../../../src/engine/renderer/opengl/render_command.cc
+ ../../../src/engine/renderer/opengl/renderer_opengl_android.cc
+ ../../../src/engine/renderer/opengl/renderer_opengl.cc
../../../src/engine/renderer/render_resource.cc
- ../../../src/engine/renderer/renderer_android.cc
../../../src/engine/renderer/renderer_types.cc
- ../../../src/engine/renderer/renderer.cc
../../../src/engine/renderer/shader.cc
../../../src/engine/renderer/texture.cc
../../../src/engine/shader_source.cc
@@ -94,8 +96,6 @@ add_library(native-activity SHARED
../../../src/third_party/jsoncpp/jsoncpp.cc
../../../src/third_party/minizip/ioapi.c
../../../src/third_party/minizip/unzip.c
- ../../../src/third_party/r8b/pffft.cpp
- ../../../src/third_party/r8b/r8bbase.cpp
../../../src/third_party/texture_compressor/dxt_encoder_internals.cc
../../../src/third_party/texture_compressor/dxt_encoder.cc
../../../src/third_party/texture_compressor/texture_compressor_etc1.cc
@@ -103,24 +103,23 @@ add_library(native-activity SHARED
)
if (ANDROID_ABI STREQUAL armeabi-v7a)
- target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
- target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
- set_source_files_properties(../../../src/third_party/r8b/pffft.cpp PROPERTIES COMPILE_FLAGS -mfpu=neon)
+ target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
+ target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
set_source_files_properties(../../../src/third_party/texture_compressor/dxt_encoder_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
set_source_files_properties(../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
endif()
if (ANDROID_ABI STREQUAL arm64-v8a)
- target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
- target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
+ target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc)
+ target_sources(kaliber PRIVATE ../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc)
endif()
-target_include_directories(native-activity PRIVATE
+target_include_directories(kaliber PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue
)
# add lib dependencies
-target_link_libraries(native-activity
+target_link_libraries(kaliber
android
native_app_glue
oboe
diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle
index aee51b9..5cd8399 100644
--- a/build/android/app/build.gradle
+++ b/build/android/app/build.gradle
@@ -2,22 +2,29 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 29
+ ndkVersion '21.3.6528147'
defaultConfig {
- applicationId = 'com.example.native_activity'
- minSdkVersion 14
- targetSdkVersion 28
+ applicationId = 'com.kaliber.demo'
+ minSdkVersion 21
+ targetSdkVersion 29
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
+ ndk {
+ abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
+ }
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
+ ndk {
+ debugSymbolLevel 'FULL'
+ }
}
}
externalNativeBuild {
@@ -37,4 +44,5 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.gms:play-services-ads:19.1.0'
}
diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml
index 11dc6af..bd36cb6 100644
--- a/build/android/app/src/main/AndroidManifest.xml
+++ b/build/android/app/src/main/AndroidManifest.xml
@@ -1,36 +1,53 @@
-
+ package="com.kaliber.demo"
+ android:versionCode="1"
+ android:versionName="1.0">
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/build/android/app/src/main/java/com/kaliber/base/KaliberActivity.java b/build/android/app/src/main/java/com/kaliber/base/KaliberActivity.java
new file mode 100644
index 0000000..6f21b86
--- /dev/null
+++ b/build/android/app/src/main/java/com/kaliber/base/KaliberActivity.java
@@ -0,0 +1,132 @@
+package com.kaliber.base;
+
+import android.app.NativeActivity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.StrictMode;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+
+import com.google.android.gms.ads.AdListener;
+import com.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.InterstitialAd;
+import com.kaliber.demo.R;
+
+import java.io.File;
+
+public class KaliberActivity extends NativeActivity {
+ private static final Handler sHandler = new Handler(Looper.getMainLooper());
+
+ static {
+ // Get the native Java methods bound to exported functions.
+ System.loadLibrary("kaliber");
+ }
+
+ private InterstitialAd mInterstitialAd;
+
+ public static native void onShowAdResult(boolean succeeded);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mInterstitialAd = newInterstitialAd();
+ loadInterstitialAd();
+ }
+
+ public void setKeepScreenOn(final boolean keepScreenOn) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (keepScreenOn) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+ });
+ }
+
+ private InterstitialAd newInterstitialAd() {
+ InterstitialAd interstitialAd = new InterstitialAd(this);
+ interstitialAd.setAdUnitId(getString(R.string.interstitial_ad_unit_id));
+ interstitialAd.setAdListener(new AdListener() {
+ @Override
+ public void onAdLoaded() {
+ Log.w("kaliber", "Ad loaded.");
+ }
+
+ @Override
+ public void onAdFailedToLoad(int errorCode) {
+ Log.w("kaliber", "Ad failed to load. errorCode: " + errorCode);
+ sHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (!mInterstitialAd.isLoaded())
+ loadInterstitialAd();
+ }
+ }, 1000 * 10);
+ }
+
+ @Override
+ public void onAdClosed() {
+ loadInterstitialAd();
+ onShowAdResult(true);
+ }
+ });
+ return interstitialAd;
+ }
+
+ public void showInterstitialAd() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mInterstitialAd.isLoaded()) {
+ mInterstitialAd.show();
+ } else {
+ loadInterstitialAd();
+ onShowAdResult(false);
+ }
+ }
+ });
+ }
+
+ public void shareFile(final String fileName) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
+ StrictMode.setVmPolicy(builder.build());
+
+ File dir = getExternalFilesDir(null);
+ File file = new File(dir, fileName);
+ Uri uri = FileProvider.getUriForFile(KaliberActivity.this,
+ "com.codepath.fileprovider", file);
+
+ Intent emailIntent = new Intent();
+ emailIntent.setAction(Intent.ACTION_SEND);
+ emailIntent.setType("text/plain");
+ emailIntent.putExtra(Intent.EXTRA_STREAM, uri);
+ startActivity(Intent.createChooser(emailIntent, "Send to..."));
+ } catch (Throwable t) {
+ Toast.makeText(KaliberActivity.this, "Request failed: " + t.toString(),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+ }
+
+ private void loadInterstitialAd() {
+ if (!mInterstitialAd.isLoading()) {
+ AdRequest adRequest = new AdRequest.Builder()
+ .setRequestAgent("android_studio:ad_template").build();
+ mInterstitialAd.loadAd(adRequest);
+ }
+ }
+}
diff --git a/build/android/app/src/main/res/values/strings.xml b/build/android/app/src/main/res/values/strings.xml
index d8f5513..102a7ee 100644
--- a/build/android/app/src/main/res/values/strings.xml
+++ b/build/android/app/src/main/res/values/strings.xml
@@ -1,4 +1,5 @@
- NativeActivity
+ demo
+ ca-app-pub-3940256099942544/1033173712
diff --git a/build/android/app/src/main/res/xml/file_paths.xml b/build/android/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..503aebe
--- /dev/null
+++ b/build/android/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/build/android/build.gradle b/build/android/build.gradle
index b4f5ce0..c032b58 100644
--- a/build/android/build.gradle
+++ b/build/android/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.2'
+ classpath 'com.android.tools.build:gradle:4.1.0'
}
}
diff --git a/build/android/gradle/wrapper/gradle-wrapper.properties b/build/android/gradle/wrapper/gradle-wrapper.properties
index 48c4576..401bb9f 100644
--- a/build/android/gradle/wrapper/gradle-wrapper.properties
+++ b/build/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Sun Feb 05 19:39:12 IST 2017
+#Thu Oct 15 21:41:36 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
diff --git a/build/android/local.properties b/build/android/local.properties
index b03a2b4..9ee0358 100644
--- a/build/android/local.properties
+++ b/build/android/local.properties
@@ -4,5 +4,6 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
-#Thu Apr 09 18:23:32 CEST 2020
-sdk.dir=/home/auygun/Android/Sdk
+#Fri Oct 16 12:48:26 CEST 2020
+ndk.dir=/home/auygun/spinning/Android/Sdk/ndk/21.3.6528147
+sdk.dir=/home/auygun/spinning/Android/Sdk
diff --git a/build/linux/Makefile b/build/linux/Makefile
index b102b52..681a1af 100644
--- a/build/linux/Makefile
+++ b/build/linux/Makefile
@@ -68,6 +68,7 @@ GLTEST_SRC := \
$(SRC_ROOT)/base/collusion_test.cc \
$(SRC_ROOT)/base/log.cc \
$(SRC_ROOT)/base/random.cc \
+ $(SRC_ROOT)/base/sinc_resampler.cc \
$(SRC_ROOT)/base/task_runner.cc \
$(SRC_ROOT)/base/timer.cc \
$(SRC_ROOT)/base/vecmath.cc \
@@ -90,16 +91,17 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/image_quad.cc \
$(SRC_ROOT)/engine/image.cc \
$(SRC_ROOT)/engine/mesh.cc \
+ $(SRC_ROOT)/engine/persistent_data.cc \
$(SRC_ROOT)/engine/platform/asset_file_linux.cc \
$(SRC_ROOT)/engine/platform/asset_file.cc \
$(SRC_ROOT)/engine/platform/platform_base.cc \
$(SRC_ROOT)/engine/platform/platform_linux.cc \
$(SRC_ROOT)/engine/renderer/geometry.cc \
- $(SRC_ROOT)/engine/renderer/render_command.cc \
+ $(SRC_ROOT)/engine/renderer/opengl/render_command.cc \
$(SRC_ROOT)/engine/renderer/render_resource.cc \
- $(SRC_ROOT)/engine/renderer/renderer_linux.cc \
+ $(SRC_ROOT)/engine/renderer/opengl/renderer_opengl_linux.cc \
+ $(SRC_ROOT)/engine/renderer/opengl/renderer_opengl.cc \
$(SRC_ROOT)/engine/renderer/renderer_types.cc \
- $(SRC_ROOT)/engine/renderer/renderer.cc \
$(SRC_ROOT)/engine/renderer/shader.cc \
$(SRC_ROOT)/engine/renderer/texture.cc \
$(SRC_ROOT)/engine/shader_source.cc \
@@ -108,8 +110,6 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/sound.cc \
$(SRC_ROOT)/third_party/glew/glew.c \
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \
- $(SRC_ROOT)/third_party/r8b/pffft.cpp \
- $(SRC_ROOT)/third_party/r8b/r8bbase.cpp \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder_internals.cc \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder.cc \
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor_etc1.cc \
diff --git a/build/travis.sh b/build/travis.sh
deleted file mode 100644
index 10c048a..0000000
--- a/build/travis.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-
-sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
-sudo apt-get update -qq
-
-sudo apt-get install -qq g++-7
-sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 90
-
-sudo apt-get install -qq -y libasound2-dev
-
-sudo apt-get install -qq build-essential xorg-dev libc++-dev
diff --git a/privacy.md b/privacy.md
new file mode 100644
index 0000000..bfc6539
--- /dev/null
+++ b/privacy.md
@@ -0,0 +1,65 @@
+**Privacy Policy**
+
+Attila Uygun built the Woom app as an Ad Supported app. This SERVICE is provided by Attila Uygun at no cost and is intended for use as is.
+
+This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
+
+If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
+
+The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Woom unless otherwise defined in this Privacy Policy.
+
+**Information Collection and Use**
+
+For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.
+
+The app does use third party services that may collect information used to identify you.
+
+Link to privacy policy of third party service providers used by the app
+
+* [Google Play Services](https://www.google.com/policies/privacy/)
+* [AdMob](https://support.google.com/admob/answer/6128543?hl=en)
+
+**Log Data**
+
+I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
+
+**Cookies**
+
+Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
+
+This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
+
+**Service Providers**
+
+I may employ third-party companies and individuals due to the following reasons:
+
+* To facilitate our Service;
+* To provide the Service on our behalf;
+* To perform Service-related services; or
+* To assist us in analyzing how our Service is used.
+
+I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
+
+**Security**
+
+I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
+
+**Links to Other Sites**
+
+This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
+
+**Children’s Privacy**
+
+These Services do not address anyone under the age of 13. I do not knowingly collect personally identifiable information from children under 13\. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.
+
+**Changes to This Privacy Policy**
+
+I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
+
+This policy is effective as of 2020-10-15
+
+**Contact Us**
+
+If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at coldreboot.se@gmail.com.
+
+This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/)
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 16a9485..6603e69 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -12,6 +12,8 @@ source_set("base") {
"misc.h",
"random.cc",
"random.h",
+ "sinc_resampler.cc",
+ "sinc_resampler.h",
"task_runner.cc",
"task_runner.h",
"timer.cc",
diff --git a/src/base/closure.h b/src/base/closure.h
index 9ae9727..ede8f55 100644
--- a/src/base/closure.h
+++ b/src/base/closure.h
@@ -2,6 +2,7 @@
#define CLOSURE_H
#include
+#include
#include
#include
@@ -43,6 +44,16 @@ using Location = std::nullptr_t;
#endif
+// Bind a method to an object with a std::weak_ptr.
+template
+std::function BindWeak(ReturnType (Class::*func)(Args...),
+ std::weak_ptr weak_ptr) {
+ return [func, weak_ptr](Args... args) {
+ if (auto ptr = weak_ptr.lock())
+ std::invoke(func, ptr, args...);
+ };
+}
+
} // namespace base
#endif // CLOSURE_H
diff --git a/src/base/log.cc b/src/base/log.cc
index 16d62b0..226b020 100644
--- a/src/base/log.cc
+++ b/src/base/log.cc
@@ -6,24 +6,17 @@
#include
#endif
#include
-#include
-#include
-
-#include "vecmath.h"
namespace base {
-// Adapted from Chromium's logging implementation.
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have
// an object of the correct type on the LHS of the unused part of the ternary
// operator.
-LogBase* LogBase::swallow_stream;
+std::ostream* LogMessage::swallow_stream;
-LogBase::LogBase(const char* file, int line) : file_(file), line_(line) {}
+LogMessage::LogMessage(const char* file, int line) : file_(file), line_(line) {}
-LogBase::~LogBase() = default;
-
-void LogBase::Flush() {
+LogMessage::~LogMessage() {
stream_ << std::endl;
std::string text(stream_.str());
std::string filename(file_);
@@ -38,78 +31,31 @@ void LogBase::Flush() {
#endif
}
-Log::Log(const char* file, int line) : LogBase(file, line) {}
-
-Log::~Log() {
- Flush();
+LogAbort LogAbort::Check(const char* file, int line, const char* expr) {
+ LogAbort instance(new LogMessage(file, line));
+ instance.GetLog().stream() << "CHECK: "
+ << "(" << expr << ") ";
+ return instance;
}
-LogDiff::LogDiff(const char* file, int line) : LogBase(file, line) {}
-
-LogDiff::~LogDiff() {
- static std::unordered_map log_map;
- static std::mutex lock;
-
- auto key = std::string(file_) + std::to_string(line_);
- bool flush = true;
- {
- std::lock_guard scoped_lock(lock);
- auto it = log_map.find(key);
- if (it == log_map.end())
- log_map[key] = stream_.str();
- else if (it->second != stream_.str())
- it->second = stream_.str();
- else
- flush = false;
- }
-
- if (flush)
- Flush();
+LogAbort LogAbort::DCheck(const char* file, int line, const char* expr) {
+ LogAbort instance(new LogMessage(file, line));
+ instance.GetLog().stream() << "DCHECK: "
+ << "(" << expr << ") ";
+ return instance;
}
-Check::Check(const char* file,
- int line,
- bool condition,
- bool debug,
- const char* expr)
- : LogBase(file, line), condition_(condition) {
- if (!condition_)
- base() << (debug ? "DCHECK: (" : "CHECK: (") << expr << ") ";
+LogAbort LogAbort::NotReached(const char* file, int line) {
+ LogAbort instance(new LogMessage(file, line));
+ instance.GetLog().stream() << "NOTREACHED ";
+ return instance;
}
-Check::~Check() {
- if (!condition_) {
- Flush();
- std::abort();
- }
-}
+LogAbort::LogAbort(LogMessage* log) : log_(log) {}
-NotReached::NotReached(const char* file, int line) : LogBase(file, line) {
- base() << "NOTREACHED ";
-}
-
-NotReached::~NotReached() {
- Flush();
+LogAbort::~LogAbort() {
+ delete log_;
std::abort();
}
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector2& arg) {
- out.stream() << "(" << arg.x << ", " << arg.y << ")";
- return out;
-}
-
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector3& arg) {
- out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ")";
- return out;
-}
-
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector4& arg) {
- out.stream() << "(" << arg.x << ", " << arg.y << ", " << arg.z << ", "
- << arg.w << ")";
- return out;
-}
-
} // namespace base
diff --git a/src/base/log.h b/src/base/log.h
index 1180a7e..2b1b974 100644
--- a/src/base/log.h
+++ b/src/base/log.h
@@ -3,114 +3,95 @@
#include
+// Adapted from Chromium's logging implementation.
+
// Macros for logging that are active in both debug and release builds. The way
// to log things is to stream things to LOG.
-// LOG_DIFF can be used to avoid spam and log only if the message differs.
+// LOG_IF can be used for conditional logging.
// CHECK(condition) terminates the process if the condition is false.
// NOTREACHED annotates unreachable codepaths and terminates the process if
// reached.
-#define LOG base::Log(__FILE__, __LINE__).base()
-#define LOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
-#define CHECK(expr) \
- base::Check(__FILE__, __LINE__, static_cast(expr), false, #expr).base()
-#define NOTREACHED base::NotReached(__FILE__, __LINE__).base()
+#define LOG base::LogMessage(__FILE__, __LINE__).stream()
+#define LOG_IF(condition) \
+ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
+#define CHECK(condition) \
+ LAZY_STREAM( \
+ !(condition), \
+ base::LogAbort::Check(__FILE__, __LINE__, #condition).GetLog().stream())
+
+#define NOTREACHED \
+ base::LogAbort::NotReached(__FILE__, __LINE__).GetLog().stream()
// Macros for logging which are active only in debug builds.
#ifdef _DEBUG
-#define DLOG base::Log(__FILE__, __LINE__).base()
-#define DLOG_DIFF base::LogDiff(__FILE__, __LINE__).base()
-#define DCHECK(expr) \
- base::Check(__FILE__, __LINE__, static_cast(expr), true, #expr).base()
+#define DLOG base::LogMessage(__FILE__, __LINE__).stream()
+#define DLOG_IF(condition) \
+ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
+#define DCHECK(condition) \
+ LAZY_STREAM(!(condition), \
+ base::LogAbort::DCheck(__FILE__, __LINE__, #condition) \
+ .GetLog() \
+ .stream())
#else
// "debug mode" logging is compiled away to nothing for release builds.
#define DLOG EAT_STREAM_PARAMETERS
-#define DLOG_DIFF EAT_STREAM_PARAMETERS
-#define DCHECK(expr) EAT_STREAM_PARAMETERS
+#define DLOG_IF(condition) EAT_STREAM_PARAMETERS
+#define DCHECK(condition) EAT_STREAM_PARAMETERS
#endif
-// Adapted from Chromium's logging implementation.
+// Helper macro which avoids evaluating the arguments to a stream if
+// the condition doesn't hold.
+#define LAZY_STREAM(condition, stream) \
+ !(condition) ? (void)0 : base::LogMessage::Voidify() & (stream)
+
// Avoid any pointless instructions to be emitted by the compiler.
#define EAT_STREAM_PARAMETERS \
- true ? (void)0 : base::LogBase::Voidify() & (*base::LogBase::swallow_stream)
+ LAZY_STREAM(false, *base::LogMessage::swallow_stream)
namespace base {
-struct Vector2;
-struct Vector3;
-struct Vector4;
-
-class LogBase {
+class LogMessage {
public:
class Voidify {
public:
Voidify() = default;
+
// This has to be an operator with a precedence lower than << but
// higher than ?:
- void operator&(LogBase&) {}
+ void operator&(std::ostream&) {}
};
- LogBase& base() { return *this; }
+ LogMessage(const char* file, int line);
+ ~LogMessage();
+
+ LogMessage& base() { return *this; }
std::ostream& stream() { return stream_; }
- static LogBase* swallow_stream;
+ static std::ostream* swallow_stream;
protected:
const char* file_;
const int line_;
std::ostringstream stream_;
-
- LogBase(const char* file, int line);
- ~LogBase();
-
- void Flush();
};
-class Log : public LogBase {
+class LogAbort {
public:
- Log(const char* file, int line);
- ~Log();
-};
+ ~LogAbort();
-class LogDiff : public LogBase {
- public:
- LogDiff(const char* file, int line);
- ~LogDiff();
-};
+ static LogAbort Check(const char* file, int line, const char* expr);
+ static LogAbort DCheck(const char* file, int line, const char* expr);
+ static LogAbort NotReached(const char* file, int line);
-class Check : public LogBase {
- public:
- Check(const char* file,
- int line,
- bool condition,
- bool debug,
- const char* expr);
- ~Check();
+ LogMessage& GetLog() { return *log_; }
private:
- bool condition_;
+ LogMessage* log_;
+
+ LogAbort(LogMessage* log);
};
-class NotReached : public LogBase {
- public:
- NotReached(const char* file, int line);
- ~NotReached();
-};
-
-template
-LogBase& operator<<(LogBase& out, const T& arg) {
- out.stream() << arg;
- return out;
-}
-
-// Explicit specialization for internal types.
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector2& arg);
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector3& arg);
-template <>
-LogBase& operator<<(LogBase& out, const base::Vector4& arg);
-
} // namespace base
#endif // LOG_H
diff --git a/src/base/random.cc b/src/base/random.cc
index c9c7066..ac0a90e 100644
--- a/src/base/random.cc
+++ b/src/base/random.cc
@@ -3,24 +3,35 @@
#include
#include "interpolation.h"
+#include "log.h"
namespace base {
Random::Random() {
std::random_device rd;
- generator_ = std::mt19937(rd());
- real_distribution_ = std::uniform_real_distribution(0, 1);
+ seed_ = rd();
+ DLOG << "Random seed: " << seed_;
+ Initialize();
}
Random::Random(unsigned seed) {
- generator_ = std::mt19937(seed);
- real_distribution_ = std::uniform_real_distribution(0, 1);
+ seed_ = seed;
+ Initialize();
}
Random::~Random() = default;
+float Random::GetFloat() {
+ return real_distribution_(generator_);
+}
+
int Random::Roll(int sides) {
return Lerp(1, sides, GetFloat());
}
+void Random::Initialize() {
+ generator_ = std::mt19937(seed_);
+ real_distribution_ = std::uniform_real_distribution(0, 1);
+}
+
} // namespace base
diff --git a/src/base/random.h b/src/base/random.h
index aab1f10..13a4bef 100644
--- a/src/base/random.h
+++ b/src/base/random.h
@@ -12,14 +12,19 @@ class Random {
~Random();
// Returns a random float between 0 and 1.
- float GetFloat() { return real_distribution_(generator_); }
+ float GetFloat();
// Roll dice with the given number of sides.
int Roll(int sides);
+ unsigned seed() const { return seed_; }
+
private:
+ unsigned seed_ = 0;
std::mt19937 generator_;
std::uniform_real_distribution real_distribution_;
+
+ void Initialize();
};
} // namespace base
diff --git a/src/base/semaphore.h b/src/base/semaphore.h
index 607714a..586ee55 100644
--- a/src/base/semaphore.h
+++ b/src/base/semaphore.h
@@ -4,6 +4,8 @@
#include
#include
+#include "../base/log.h"
+
namespace base {
class Semaphore {
@@ -14,6 +16,7 @@ class Semaphore {
std::unique_lock scoped_lock(mutex_);
cv_.wait(scoped_lock, [&]() { return count_ > 0; });
--count_;
+ DCHECK(count_ >= 0);
}
void Release() {
diff --git a/src/base/sinc_resampler.cc b/src/base/sinc_resampler.cc
new file mode 100644
index 0000000..bde1991
--- /dev/null
+++ b/src/base/sinc_resampler.cc
@@ -0,0 +1,446 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Initial input buffer layout, dividing into regions r0_ to r4_ (note: r0_, r3_
+// and r4_ will move after the first load):
+//
+// |----------------|-----------------------------------------|----------------|
+//
+// request_frames_
+// <--------------------------------------------------------->
+// r0_ (during first load)
+//
+// kKernelSize / 2 kKernelSize / 2 kKernelSize / 2 kKernelSize / 2
+// <---------------> <---------------> <---------------> <--------------->
+// r1_ r2_ r3_ r4_
+//
+// block_size_ == r4_ - r2_
+// <--------------------------------------->
+//
+// request_frames_
+// <------------------ ... ----------------->
+// r0_ (during second load)
+//
+// On the second request r0_ slides to the right by kKernelSize / 2 and r3_, r4_
+// and block_size_ are reinitialized via step (3) in the algorithm below.
+//
+// These new regions remain constant until a Flush() occurs. While complicated,
+// this allows us to reduce jitter by always requesting the same amount from the
+// provided callback.
+//
+// The algorithm:
+//
+// 1) Allocate input_buffer of size: request_frames_ + kKernelSize; this ensures
+// there's enough room to read request_frames_ from the callback into region
+// r0_ (which will move between the first and subsequent passes).
+//
+// 2) Let r1_, r2_ each represent half the kernel centered around r0_:
+//
+// r0_ = input_buffer_ + kKernelSize / 2
+// r1_ = input_buffer_
+// r2_ = r0_
+//
+// r0_ is always request_frames_ in size. r1_, r2_ are kKernelSize / 2 in
+// size. r1_ must be zero initialized to avoid convolution with garbage (see
+// step (5) for why).
+//
+// 3) Let r3_, r4_ each represent half the kernel right aligned with the end of
+// r0_ and choose block_size_ as the distance in frames between r4_ and r2_:
+//
+// r3_ = r0_ + request_frames_ - kKernelSize
+// r4_ = r0_ + request_frames_ - kKernelSize / 2
+// block_size_ = r4_ - r2_ = request_frames_ - kKernelSize / 2
+//
+// 4) Consume request_frames_ frames into r0_.
+//
+// 5) Position kernel centered at start of r2_ and generate output frames until
+// the kernel is centered at the start of r4_ or we've finished generating
+// all the output frames.
+//
+// 6) Wrap left over data from the r3_ to r1_ and r4_ to r2_.
+//
+// 7) If we're on the second load, in order to avoid overwriting the frames we
+// just wrapped from r4_ we need to slide r0_ to the right by the size of
+// r4_, which is kKernelSize / 2:
+//
+// r0_ = r0_ + kKernelSize / 2 = input_buffer_ + kKernelSize
+//
+// r3_, r4_, and block_size_ then need to be reinitialized, so goto (3).
+//
+// 8) Else, if we're not on the second load, goto (4).
+//
+// Note: we're glossing over how the sub-sample handling works with
+// |virtual_source_idx_|, etc.
+
+#include "sinc_resampler.h"
+
+#include
+#include
+#include
+
+#include "log.h"
+
+#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
+#include
+#define CONVOLVE_FUNC Convolve_SSE
+#elif defined(_M_ARM64) || defined(__aarch64__)
+#include
+#define CONVOLVE_FUNC Convolve_NEON
+#else
+#define CONVOLVE_FUNC Convolve_C
+#endif
+
+namespace {
+
+constexpr double kPiDouble = 3.14159265358979323846;
+constexpr float kPiFloat = 3.14159265358979323846f;
+
+class ScopedSubnormalFloatDisabler {
+ public:
+ ScopedSubnormalFloatDisabler() {
+#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
+ // Turn on "subnormals are zero" and "flush to zero" CSR flags.
+ orig_state_ = _mm_getcsr();
+ _mm_setcsr(orig_state_ | 0x8040);
+#endif
+ }
+
+ ScopedSubnormalFloatDisabler(const ScopedSubnormalFloatDisabler&) = delete;
+
+ ~ScopedSubnormalFloatDisabler() {
+#if defined(ARCH_CPU_X86_FAMILY)
+ _mm_setcsr(orig_state_);
+#endif
+ }
+
+ ScopedSubnormalFloatDisabler& operator=(const ScopedSubnormalFloatDisabler&) =
+ delete;
+
+#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
+ private:
+ unsigned int orig_state_;
+#endif
+};
+
+double SincScaleFactor(double io_ratio) {
+ // |sinc_scale_factor| is basically the normalized cutoff frequency of the
+ // low-pass filter.
+ double sinc_scale_factor = io_ratio > 1.0 ? 1.0 / io_ratio : 1.0;
+
+ // The sinc function is an idealized brick-wall filter, but since we're
+ // windowing it the transition from pass to stop does not happen right away.
+ // So we should adjust the low pass filter cutoff slightly downward to avoid
+ // some aliasing at the very high-end.
+ // TODO(crogers): this value is empirical and to be more exact should vary
+ // depending on kKernelSize.
+ sinc_scale_factor *= 0.9;
+
+ return sinc_scale_factor;
+}
+
+int CalculateChunkSize(int block_size_, double io_ratio) {
+ return block_size_ / io_ratio;
+}
+
+} // namespace
+
+namespace base {
+
+SincResampler::SincResampler(double io_sample_rate_ratio, int request_frames)
+ : io_sample_rate_ratio_(io_sample_rate_ratio),
+ request_frames_(request_frames),
+ input_buffer_size_(request_frames_ + kKernelSize),
+ // Create input buffers with a 16-byte alignment for SSE optimizations.
+ kernel_storage_(static_cast(
+ base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
+ kernel_pre_sinc_storage_(static_cast(
+ base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
+ kernel_window_storage_(static_cast(
+ base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
+ input_buffer_(static_cast(
+ base::AlignedAlloc<16>(sizeof(float) * input_buffer_size_))),
+ r1_(input_buffer_.get()),
+ r2_(input_buffer_.get() + kKernelSize / 2) {
+ DCHECK(request_frames_ > 0);
+ Flush();
+ DCHECK(block_size_ > kKernelSize)
+ << "block_size must be greater than kKernelSize!";
+
+ memset(kernel_storage_.get(), 0,
+ sizeof(*kernel_storage_.get()) * kKernelStorageSize);
+ memset(kernel_pre_sinc_storage_.get(), 0,
+ sizeof(*kernel_pre_sinc_storage_.get()) * kKernelStorageSize);
+ memset(kernel_window_storage_.get(), 0,
+ sizeof(*kernel_window_storage_.get()) * kKernelStorageSize);
+
+ InitializeKernel();
+}
+
+SincResampler::~SincResampler() = default;
+
+void SincResampler::UpdateRegions(bool second_load) {
+ // Setup various region pointers in the buffer (see diagram above). If we're
+ // on the second load we need to slide r0_ to the right by kKernelSize / 2.
+ r0_ = input_buffer_.get() + (second_load ? kKernelSize : kKernelSize / 2);
+ r3_ = r0_ + request_frames_ - kKernelSize;
+ r4_ = r0_ + request_frames_ - kKernelSize / 2;
+ block_size_ = r4_ - r2_;
+ chunk_size_ = CalculateChunkSize(block_size_, io_sample_rate_ratio_);
+
+ // r1_ at the beginning of the buffer.
+ DCHECK(r1_ == input_buffer_.get());
+ // r1_ left of r2_, r4_ left of r3_ and size correct.
+ DCHECK(r2_ - r1_ == r4_ - r3_);
+ // r2_ left of r3.
+ DCHECK(r2_ < r3_);
+}
+
+void SincResampler::InitializeKernel() {
+ // Blackman window parameters.
+ static const double kAlpha = 0.16;
+ static const double kA0 = 0.5 * (1.0 - kAlpha);
+ static const double kA1 = 0.5;
+ static const double kA2 = 0.5 * kAlpha;
+
+ // Generates a set of windowed sinc() kernels.
+ // We generate a range of sub-sample offsets from 0.0 to 1.0.
+ const double sinc_scale_factor = SincScaleFactor(io_sample_rate_ratio_);
+ for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) {
+ const float subsample_offset =
+ static_cast(offset_idx) / kKernelOffsetCount;
+
+ for (int i = 0; i < kKernelSize; ++i) {
+ const int idx = i + offset_idx * kKernelSize;
+ const float pre_sinc =
+ kPiFloat * (i - kKernelSize / 2 - subsample_offset);
+ kernel_pre_sinc_storage_[idx] = pre_sinc;
+
+ // Compute Blackman window, matching the offset of the sinc().
+ const float x = (i - subsample_offset) / kKernelSize;
+ const float window =
+ static_cast(kA0 - kA1 * cos(2.0 * kPiDouble * x) +
+ kA2 * cos(4.0 * kPiDouble * x));
+ kernel_window_storage_[idx] = window;
+
+ // Compute the sinc with offset, then window the sinc() function and store
+ // at the correct offset.
+ kernel_storage_[idx] = static_cast(
+ window * (pre_sinc ? sin(sinc_scale_factor * pre_sinc) / pre_sinc
+ : sinc_scale_factor));
+ }
+ }
+}
+
+void SincResampler::SetRatio(double io_sample_rate_ratio) {
+ if (fabs(io_sample_rate_ratio_ - io_sample_rate_ratio) <
+ std::numeric_limits::epsilon()) {
+ return;
+ }
+
+ io_sample_rate_ratio_ = io_sample_rate_ratio;
+ chunk_size_ = CalculateChunkSize(block_size_, io_sample_rate_ratio_);
+
+ // Optimize reinitialization by reusing values which are independent of
+ // |sinc_scale_factor|. Provides a 3x speedup.
+ const double sinc_scale_factor = SincScaleFactor(io_sample_rate_ratio_);
+ for (int offset_idx = 0; offset_idx <= kKernelOffsetCount; ++offset_idx) {
+ for (int i = 0; i < kKernelSize; ++i) {
+ const int idx = i + offset_idx * kKernelSize;
+ const float window = kernel_window_storage_[idx];
+ const float pre_sinc = kernel_pre_sinc_storage_[idx];
+
+ kernel_storage_[idx] = static_cast(
+ window * (pre_sinc ? sin(sinc_scale_factor * pre_sinc) / pre_sinc
+ : sinc_scale_factor));
+ }
+ }
+}
+
+void SincResampler::Resample(int frames, float* destination, ReadCB read_cb) {
+ int remaining_frames = frames;
+
+ // Step (1) -- Prime the input buffer at the start of the input stream.
+ if (!buffer_primed_ && remaining_frames) {
+ read_cb(request_frames_, r0_);
+ buffer_primed_ = true;
+ }
+
+ // Step (2) -- Resample!
+ while (remaining_frames) {
+ // Silent audio can contain non-zero samples small enough to result in
+ // subnormals internally. Disabling subnormals can be significantly faster.
+ {
+ ScopedSubnormalFloatDisabler disable_subnormals;
+
+ while (virtual_source_idx_ < block_size_) {
+ // |virtual_source_idx_| lies in between two kernel offsets so figure
+ // out what they are.
+ const int source_idx = static_cast(virtual_source_idx_);
+ const double virtual_offset_idx =
+ (virtual_source_idx_ - source_idx) * kKernelOffsetCount;
+ const int offset_idx = static_cast(virtual_offset_idx);
+
+ // We'll compute "convolutions" for the two kernels which straddle
+ // |virtual_source_idx_|.
+ const float* k1 = kernel_storage_.get() + offset_idx * kKernelSize;
+ const float* k2 = k1 + kKernelSize;
+
+ // Ensure |k1|, |k2| are 16-byte aligned for SIMD usage. Should always
+ // be true so long as kKernelSize is a multiple of 16.
+ DCHECK(0u == (reinterpret_cast(k1) & 0x0F));
+ DCHECK(0u == (reinterpret_cast(k2) & 0x0F));
+
+ // Initialize input pointer based on quantized |virtual_source_idx_|.
+ const float* input_ptr = r1_ + source_idx;
+
+ // Figure out how much to weight each kernel's "convolution".
+ const double kernel_interpolation_factor =
+ virtual_offset_idx - offset_idx;
+ *destination++ =
+ CONVOLVE_FUNC(input_ptr, k1, k2, kernel_interpolation_factor);
+
+ // Advance the virtual index.
+ virtual_source_idx_ += io_sample_rate_ratio_;
+ if (!--remaining_frames)
+ return;
+ }
+ }
+
+ // Wrap back around to the start.
+ DCHECK(virtual_source_idx_ > block_size_);
+ virtual_source_idx_ -= block_size_;
+
+ // Step (3) -- Copy r3_, r4_ to r1_, r2_.
+ // This wraps the last input frames back to the start of the buffer.
+ memcpy(r1_, r3_, sizeof(*input_buffer_.get()) * kKernelSize);
+
+ // Step (4) -- Reinitialize regions if necessary.
+ if (r0_ == r2_)
+ UpdateRegions(true);
+
+ // Step (5) -- Refresh the buffer with more input.
+ read_cb(request_frames_, r0_);
+ }
+}
+
+void SincResampler::PrimeWithSilence() {
+ // By enforcing the buffer hasn't been primed, we ensure the input buffer has
+ // already been zeroed during construction or by a previous Flush() call.
+ DCHECK(!buffer_primed_);
+ DCHECK(input_buffer_[0] == 0.0f);
+ UpdateRegions(true);
+}
+
+void SincResampler::Flush() {
+ virtual_source_idx_ = 0;
+ buffer_primed_ = false;
+ memset(input_buffer_.get(), 0,
+ sizeof(*input_buffer_.get()) * input_buffer_size_);
+ UpdateRegions(false);
+}
+
+int SincResampler::GetMaxInputFramesRequested(
+ int output_frames_requested) const {
+ const int num_chunks = static_cast(
+ std::ceil(static_cast(output_frames_requested) / chunk_size_));
+
+ return num_chunks * request_frames_;
+}
+
+double SincResampler::BufferedFrames() const {
+ return buffer_primed_ ? request_frames_ - virtual_source_idx_ : 0;
+}
+
+float SincResampler::Convolve_C(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor) {
+ float sum1 = 0;
+ float sum2 = 0;
+
+ // Generate a single output sample. Unrolling this loop hurt performance in
+ // local testing.
+ int n = kKernelSize;
+ while (n--) {
+ sum1 += *input_ptr * *k1++;
+ sum2 += *input_ptr++ * *k2++;
+ }
+
+ // Linearly interpolate the two "convolutions".
+ return static_cast((1.0 - kernel_interpolation_factor) * sum1 +
+ kernel_interpolation_factor * sum2);
+}
+
+#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
+float SincResampler::Convolve_SSE(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor) {
+ __m128 m_input;
+ __m128 m_sums1 = _mm_setzero_ps();
+ __m128 m_sums2 = _mm_setzero_ps();
+
+ // Based on |input_ptr| alignment, we need to use loadu or load. Unrolling
+ // these loops hurt performance in local testing.
+ if (reinterpret_cast(input_ptr) & 0x0F) {
+ for (int i = 0; i < kKernelSize; i += 4) {
+ m_input = _mm_loadu_ps(input_ptr + i);
+ m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i)));
+ m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i)));
+ }
+ } else {
+ for (int i = 0; i < kKernelSize; i += 4) {
+ m_input = _mm_load_ps(input_ptr + i);
+ m_sums1 = _mm_add_ps(m_sums1, _mm_mul_ps(m_input, _mm_load_ps(k1 + i)));
+ m_sums2 = _mm_add_ps(m_sums2, _mm_mul_ps(m_input, _mm_load_ps(k2 + i)));
+ }
+ }
+
+ // Linearly interpolate the two "convolutions".
+ m_sums1 = _mm_mul_ps(
+ m_sums1,
+ _mm_set_ps1(static_cast(1.0 - kernel_interpolation_factor)));
+ m_sums2 = _mm_mul_ps(
+ m_sums2, _mm_set_ps1(static_cast(kernel_interpolation_factor)));
+ m_sums1 = _mm_add_ps(m_sums1, m_sums2);
+
+ // Sum components together.
+ float result;
+ m_sums2 = _mm_add_ps(_mm_movehl_ps(m_sums1, m_sums1), m_sums1);
+ _mm_store_ss(&result,
+ _mm_add_ss(m_sums2, _mm_shuffle_ps(m_sums2, m_sums2, 1)));
+
+ return result;
+}
+#elif defined(_M_ARM64) || defined(__aarch64__)
+float SincResampler::Convolve_NEON(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor) {
+ float32x4_t m_input;
+ float32x4_t m_sums1 = vmovq_n_f32(0);
+ float32x4_t m_sums2 = vmovq_n_f32(0);
+
+ const float* upper = input_ptr + kKernelSize;
+ for (; input_ptr < upper;) {
+ m_input = vld1q_f32(input_ptr);
+ input_ptr += 4;
+ m_sums1 = vmlaq_f32(m_sums1, m_input, vld1q_f32(k1));
+ k1 += 4;
+ m_sums2 = vmlaq_f32(m_sums2, m_input, vld1q_f32(k2));
+ k2 += 4;
+ }
+
+ // Linearly interpolate the two "convolutions".
+ m_sums1 = vmlaq_f32(
+ vmulq_f32(m_sums1, vmovq_n_f32(1.0 - kernel_interpolation_factor)),
+ m_sums2, vmovq_n_f32(kernel_interpolation_factor));
+
+ // Sum components together.
+ float32x2_t m_half = vadd_f32(vget_high_f32(m_sums1), vget_low_f32(m_sums1));
+ return vget_lane_f32(vpadd_f32(m_half, m_half), 0);
+}
+#endif
+
+} // namespace base
diff --git a/src/base/sinc_resampler.h b/src/base/sinc_resampler.h
new file mode 100644
index 0000000..b6102e1
--- /dev/null
+++ b/src/base/sinc_resampler.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SINC_RESAMPLER_H_
+#define SINC_RESAMPLER_H_
+
+#include
+#include
+
+#include "mem.h"
+
+namespace base {
+
+// SincResampler is a high-quality single-channel sample-rate converter.
+class SincResampler {
+ public:
+ enum {
+ // The kernel size can be adjusted for quality (higher is better) at the
+ // expense of performance. Must be a multiple of 32.
+ // TODO(dalecurtis): Test performance to see if we can jack this up to 64+.
+ kKernelSize = 32,
+
+ // Default request size. Affects how often and for how much SincResampler
+ // calls back for input. Must be greater than kKernelSize.
+ kDefaultRequestSize = 512,
+
+ // The kernel offset count is used for interpolation and is the number of
+ // sub-sample kernel shifts. Can be adjusted for quality (higher is better)
+ // at the expense of allocating more memory.
+ kKernelOffsetCount = 32,
+ kKernelStorageSize = kKernelSize * (kKernelOffsetCount + 1),
+ };
+
+ // Callback type for providing more data into the resampler. Expects |frames|
+ // of data to be rendered into |destination|; zero padded if not enough frames
+ // are available to satisfy the request.
+ typedef std::function ReadCB;
+
+ // Constructs a SincResampler. |io_sample_rate_ratio| is the ratio
+ // of input / output sample rates. |request_frames| controls the size in
+ // frames of the buffer requested by each |read_cb| call. The value must be
+ // greater than kKernelSize. Specify kDefaultRequestSize if there are no
+ // request size constraints.
+ SincResampler(double io_sample_rate_ratio, int request_frames);
+ ~SincResampler();
+
+ // Resample |frames| of data from |read_cb| into |destination|.
+ void Resample(int frames, float* destination, ReadCB read_cb);
+
+ // The maximum size in frames that guarantees Resample() will only make a
+ // single call to |read_cb_| for more data. Note: If PrimeWithSilence() is
+ // not called, chunk size will grow after the first two Resample() calls by
+ // kKernelSize / (2 * io_sample_rate_ratio). See the .cc file for details.
+ int ChunkSize() const { return chunk_size_; }
+
+ // Returns the max number of frames that could be requested (via multiple
+ // calls to |read_cb_|) during one Resample(|output_frames_requested|) call.
+ int GetMaxInputFramesRequested(int output_frames_requested) const;
+
+ // Guarantees that ChunkSize() will not change between calls by initializing
+ // the input buffer with silence. Note, this will cause the first few samples
+ // of output to be biased towards silence. Must be called again after Flush().
+ void PrimeWithSilence();
+
+ // Flush all buffered data and reset internal indices. Not thread safe, do
+ // not call while Resample() is in progress. Note, if PrimeWithSilence() was
+ // previously called it must be called again after the Flush().
+ void Flush();
+
+ // Update |io_sample_rate_ratio_|. SetRatio() will cause a reconstruction of
+ // the kernels used for resampling. Not thread safe, do not call while
+ // Resample() is in progress.
+ void SetRatio(double io_sample_rate_ratio);
+
+ float* get_kernel_for_testing() { return kernel_storage_.get(); }
+
+ // Return number of input frames consumed by a callback but not yet processed.
+ // Since input/output ratio can be fractional, so can this value.
+ // Zero before first call to Resample().
+ double BufferedFrames() const;
+
+ private:
+ void InitializeKernel();
+ void UpdateRegions(bool second_load);
+
+ // Compute convolution of |k1| and |k2| over |input_ptr|, resultant sums are
+ // linearly interpolated using |kernel_interpolation_factor|. On x86, the
+ // underlying implementation is chosen at run time based on SSE support. On
+ // ARM, NEON support is chosen at compile time based on compilation flags.
+ static float Convolve_C(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor);
+#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
+ static float Convolve_SSE(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor);
+#elif defined(_M_ARM64) || defined(__aarch64__)
+ static float Convolve_NEON(const float* input_ptr,
+ const float* k1,
+ const float* k2,
+ double kernel_interpolation_factor);
+#endif
+
+ // The ratio of input / output sample rates.
+ double io_sample_rate_ratio_;
+
+ // An index on the source input buffer with sub-sample precision. It must be
+ // double precision to avoid drift.
+ double virtual_source_idx_;
+
+ // The buffer is primed once at the very beginning of processing.
+ bool buffer_primed_;
+
+ // The size (in samples) to request from each |read_cb_| execution.
+ const int request_frames_;
+
+ // The number of source frames processed per pass.
+ int block_size_;
+
+ // Cached value used for ChunkSize(). The maximum size in frames that
+ // guarantees Resample() will only ask for input at most once.
+ int chunk_size_;
+
+ // The size (in samples) of the internal buffer used by the resampler.
+ const int input_buffer_size_;
+
+ // Contains kKernelOffsetCount kernels back-to-back, each of size kKernelSize.
+ // The kernel offsets are sub-sample shifts of a windowed sinc shifted from
+ // 0.0 to 1.0 sample.
+ base::AlignedMemPtr kernel_storage_;
+ base::AlignedMemPtr kernel_pre_sinc_storage_;
+ base::AlignedMemPtr kernel_window_storage_;
+
+ // Data from the source is copied into this buffer for each processing pass.
+ base::AlignedMemPtr input_buffer_;
+
+ // Pointers to the various regions inside |input_buffer_|. See the diagram at
+ // the top of the .cc file for more information.
+ float* r0_;
+ float* const r1_;
+ float* const r2_;
+ float* r3_;
+ float* r4_;
+
+ SincResampler(SincResampler const&) = delete;
+ SincResampler& operator=(SincResampler const&) = delete;
+};
+
+} // namespace base
+
+#endif // SINC_RESAMPLER_H_
diff --git a/src/base/task_runner.cc b/src/base/task_runner.cc
index 3dc9d50..b774bc3 100644
--- a/src/base/task_runner.cc
+++ b/src/base/task_runner.cc
@@ -4,14 +4,14 @@
namespace {
-void EnqueueTaskAndReplyRelay(const base::Location& from,
- base::Closure task_cb,
- base::Closure reply_cb,
- base::TaskRunner* destination) {
+void PostTaskAndReplyRelay(base::Location from,
+ base::Closure task_cb,
+ base::Closure reply_cb,
+ base::TaskRunner* destination) {
task_cb();
if (reply_cb)
- destination->EnqueueTask(from, std::move(reply_cb));
+ destination->PostTask(from, std::move(reply_cb));
}
} // namespace
@@ -30,25 +30,24 @@ TaskRunner* TaskRunner::GetThreadLocalTaskRunner() {
return thread_local_task_runner.get();
}
-void TaskRunner::EnqueueTask(const Location& from, Closure task) {
+void TaskRunner::PostTask(const Location& from, Closure task) {
DCHECK(task) << LOCATION(from);
+ task_count_.fetch_add(1, std::memory_order_relaxed);
std::lock_guard scoped_lock(lock_);
queue_.emplace_back(from, std::move(task));
}
-void TaskRunner::EnqueueTaskAndReply(const Location& from,
- Closure task,
- Closure reply) {
+void TaskRunner::PostTaskAndReply(const Location& from,
+ Closure task,
+ Closure reply) {
DCHECK(task) << LOCATION(from);
DCHECK(reply) << LOCATION(from);
DCHECK(thread_local_task_runner) << LOCATION(from);
- auto relay = std::bind(::EnqueueTaskAndReplyRelay, from, std::move(task),
+ auto relay = std::bind(::PostTaskAndReplyRelay, from, std::move(task),
std::move(reply), thread_local_task_runner.get());
-
- std::lock_guard scoped_lock(lock_);
- queue_.emplace_back(from, std::move(relay));
+ PostTask(from, std::move(relay));
}
void TaskRunner::MultiConsumerRun() {
@@ -69,6 +68,7 @@ void TaskRunner::MultiConsumerRun() {
#endif
task_cb();
+ task_count_.fetch_sub(1, std::memory_order_relaxed);
}
}
@@ -90,7 +90,16 @@ void TaskRunner::SingleConsumerRun() {
#endif
task_cb();
+ task_count_.fetch_sub(1, std::memory_order_relaxed);
}
+ cv_.notify_one();
+}
+
+void TaskRunner::WaitForCompletion() {
+ std::unique_lock scoped_lock(lock_);
+ cv_.wait(scoped_lock, [&]() -> bool {
+ return task_count_.load(std::memory_order_relaxed) == 0;
+ });
}
} // namespace base
diff --git a/src/base/task_runner.h b/src/base/task_runner.h
index 3c46af7..581c008 100644
--- a/src/base/task_runner.h
+++ b/src/base/task_runner.h
@@ -1,6 +1,8 @@
#ifndef TASK_RUNNER_H
#define TASK_RUNNER_H
+#include
+#include
#include
#include
#include
@@ -25,7 +27,7 @@ void ReturnAsParamAdapter(std::function func,
template
void ReplyAdapter(std::function callback,
ReturnType* result) {
- callback(std::move(*result));
+ callback(*result);
delete result;
}
@@ -41,16 +43,16 @@ class TaskRunner {
TaskRunner() = default;
~TaskRunner() = default;
- void EnqueueTask(const Location& from, Closure task);
+ void PostTask(const Location& from, Closure task);
- void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
+ void PostTaskAndReply(const Location& from, Closure task, Closure reply);
template
- void EnqueueTaskAndReplyWithResult(const Location& from,
- std::function task,
- std::function reply) {
+ void PostTaskAndReplyWithResult(const Location& from,
+ std::function task,
+ std::function reply) {
auto* result = new ReturnType;
- return EnqueueTaskAndReply(
+ return PostTaskAndReply(
from,
std::bind(internal::ReturnAsParamAdapter, std::move(task),
result),
@@ -62,6 +64,8 @@ class TaskRunner {
void SingleConsumerRun();
+ void WaitForCompletion();
+
static void CreateThreadLocalTaskRunner();
static TaskRunner* GetThreadLocalTaskRunner();
@@ -70,6 +74,8 @@ class TaskRunner {
std::deque queue_;
mutable std::mutex lock_;
+ std::condition_variable cv_;
+ std::atomic task_count_{0};
static thread_local std::unique_ptr thread_local_task_runner;
diff --git a/src/base/timer.cc b/src/base/timer.cc
index e30b30f..90fc86b 100644
--- a/src/base/timer.cc
+++ b/src/base/timer.cc
@@ -1,5 +1,7 @@
#include "timer.h"
+#include
+
namespace base {
Timer::Timer() {
@@ -25,4 +27,20 @@ void Timer::Update() {
seconds_accumulated_ += seconds_passed_;
}
+void Timer::Sleep(float duration) {
+ Timer timer;
+ float accumulator = 0.0;
+ constexpr float epsilon = 0.0001f;
+
+ while (accumulator < duration) {
+ timer.Update();
+ accumulator += timer.GetSecondsPassed();
+ if (duration - accumulator > epsilon) {
+ float sleep_time = duration - accumulator - epsilon;
+ std::this_thread::sleep_for(
+ std::chrono::microseconds((int)(sleep_time * 1000000.0f)));
+ }
+ };
+}
+
} // namespace base
diff --git a/src/base/timer.h b/src/base/timer.h
index 24d222f..569e8ff 100644
--- a/src/base/timer.h
+++ b/src/base/timer.h
@@ -14,6 +14,8 @@ class Timer {
void Update();
+ static void Sleep(float duration);
+
float GetSecondsPassed() const { return seconds_passed_; }
float GetSecondsAccumulated() const { return seconds_accumulated_; }
diff --git a/src/base/vecmath.cc b/src/base/vecmath.cc
index 126106b..6f012a1 100644
--- a/src/base/vecmath.cc
+++ b/src/base/vecmath.cc
@@ -1,7 +1,13 @@
#include "vecmath.h"
+using namespace std::string_literals;
+
namespace base {
+std::string Vector2::ToString() {
+ return "("s + std::to_string(x) + ", "s + std::to_string(y) + ")"s;
+}
+
Matrix4x4 Ortho(float left, float right, float bottom, float top) {
Matrix4x4 m(1);
m.c[0].x = 2.0f / (right - left);
diff --git a/src/base/vecmath.h b/src/base/vecmath.h
index 6649a3d..8310d40 100644
--- a/src/base/vecmath.h
+++ b/src/base/vecmath.h
@@ -3,6 +3,7 @@
#include
#include
+#include
namespace base {
@@ -64,6 +65,8 @@ struct Vector2 {
}
const float* GetData() const { return &x; }
+
+ std::string ToString();
};
inline Vector2 operator+(const Vector2& v1, const Vector2& v2) {
@@ -130,7 +133,7 @@ struct Vector4 {
};
inline Vector4 operator*(const Vector4& v1, const Vector4& v2) {
- return Vector4(v1.x * v2.x, v2.y * v2.y, v1.z * v2.z, v1.w * v2.w);
+ return Vector4(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z, v1.w * v2.w);
}
inline Vector4 operator*(const Vector4& v, float s) {
diff --git a/src/base/worker.cc b/src/base/worker.cc
index b3beb99..910647f 100644
--- a/src/base/worker.cc
+++ b/src/base/worker.cc
@@ -40,19 +40,19 @@ void Worker::Shutdown() {
threads_.clear();
}
-void Worker::EnqueueTask(const Location& from, Closure task) {
+void Worker::PostTask(const Location& from, Closure task) {
DCHECK((!threads_.empty()));
- task_runner_.EnqueueTask(from, std::move(task));
+ task_runner_.PostTask(from, std::move(task));
semaphore_.Release();
}
-void Worker::EnqueueTaskAndReply(const Location& from,
- Closure task,
- Closure reply) {
+void Worker::PostTaskAndReply(const Location& from,
+ Closure task,
+ Closure reply) {
DCHECK((!threads_.empty()));
- task_runner_.EnqueueTaskAndReply(from, std::move(task), std::move(reply));
+ task_runner_.PostTaskAndReply(from, std::move(task), std::move(reply));
semaphore_.Release();
}
diff --git a/src/base/worker.h b/src/base/worker.h
index d9e5cc8..97761b3 100644
--- a/src/base/worker.h
+++ b/src/base/worker.h
@@ -26,16 +26,16 @@ class Worker {
void Shutdown();
- void EnqueueTask(const Location& from, Closure task);
+ void PostTask(const Location& from, Closure task);
- void EnqueueTaskAndReply(const Location& from, Closure task, Closure reply);
+ void PostTaskAndReply(const Location& from, Closure task, Closure reply);
template
- void EnqueueTaskAndReplyWithResult(const Location& from,
- std::function task,
- std::function reply) {
- task_runner_.EnqueueTaskAndReplyWithResult(from, std::move(task),
- std::move(reply));
+ void PostTaskAndReplyWithResult(const Location& from,
+ std::function task,
+ std::function reply) {
+ task_runner_.PostTaskAndReplyWithResult(from, std::move(task),
+ std::move(reply));
semaphore_.Release();
}
diff --git a/src/demo/credits.cc b/src/demo/credits.cc
index f688948..5c5e55c 100644
--- a/src/demo/credits.cc
+++ b/src/demo/credits.cc
@@ -47,10 +47,6 @@ bool Credits::Initialize() {
return true;
}
-void Credits::Update(float delta_time) {
- text_animator_.Update(delta_time);
-}
-
void Credits::OnInputEvent(std::unique_ptr event) {
if ((event->GetType() == InputEvent::kDragEnd ||
event->GetType() == InputEvent::kNavigateBack) &&
@@ -67,19 +63,19 @@ void Credits::Show() {
for (int i = 0; i < kNumLines; ++i) {
text_[i].Create("credits", {1, kNumLines});
text_[i].SetZOrder(50);
- text_[i].SetOffset({0, 0});
+ text_[i].SetPosition({0, 0});
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
text_[i].SetFrame(i);
if (i > 0) {
text_[i].PlaceToBottomOf(text_[i - 1]);
- text_[i].Translate(text_[i - 1].GetOffset() * Vector2(0, 1));
- text_[i].Translate({0, text_[i - 1].GetScale().y * -kLineSpaces[i - 1]});
+ text_[i].Translate(text_[i - 1].GetPosition() * Vector2(0, 1));
+ text_[i].Translate({0, text_[i - 1].GetSize().y * -kLineSpaces[i - 1]});
}
}
float center_offset_y =
- (text_[0].GetOffset().y - text_[kNumLines - 1].GetOffset().y) / 2;
+ (text_[0].GetPosition().y - text_[kNumLines - 1].GetPosition().y) / 2;
for (int i = 0; i < kNumLines; ++i)
text_[i].Translate({0, center_offset_y});
diff --git a/src/demo/credits.h b/src/demo/credits.h
index 166232f..afa83c7 100644
--- a/src/demo/credits.h
+++ b/src/demo/credits.h
@@ -21,8 +21,6 @@ class Credits {
bool Initialize();
- void Update(float delta_time);
-
void OnInputEvent(std::unique_ptr event);
void Show();
diff --git a/src/demo/demo.cc b/src/demo/demo.cc
index c2cd784..cf69eca 100644
--- a/src/demo/demo.cc
+++ b/src/demo/demo.cc
@@ -83,9 +83,9 @@ void Demo::Update(float delta_time) {
hud_.SetScore(score_, true);
}
- hud_.Update(delta_time);
- menu_.Update(delta_time);
- credits_.Update(delta_time);
+ sky_.Update(delta_time);
+ player_.Update(delta_time);
+ enemy_.Update(delta_time);
if (state_ == kMenu)
UpdateMenuState(delta_time);
@@ -102,7 +102,7 @@ void Demo::LostFocus() {
EnterMenuState();
}
-void Demo::GainedFocus() {}
+void Demo::GainedFocus(bool from_interstitial_ad) {}
void Demo::AddScore(int score) {
add_score_ += score;
@@ -111,6 +111,13 @@ void Demo::AddScore(int score) {
void Demo::EnterMenuState() {
if (state_ == kMenu)
return;
+
+ if (state_ == kState_Invalid || state_ == kGame) {
+ sky_.SetSpeed(0);
+ player_.Pause(true);
+ enemy_.Pause(true);
+ }
+
if (wave_ == 0) {
menu_.SetOptionEnabled(Menu::kContinue, false);
} else {
@@ -131,7 +138,11 @@ void Demo::EnterCreditsState() {
void Demo::EnterGameState() {
if (state_ == kGame)
return;
+
+ sky_.SetSpeed(0.04f);
hud_.Show();
+ player_.Pause(false);
+ enemy_.Pause(false);
state_ = kGame;
}
@@ -160,10 +171,6 @@ void Demo::UpdateMenuState(float delta_time) {
}
void Demo::UpdateGameState(float delta_time) {
- sky_.Update(delta_time);
- player_.Update(delta_time);
- enemy_.Update(delta_time);
-
if (waiting_for_next_wave_)
return;
diff --git a/src/demo/demo.h b/src/demo/demo.h
index a8f1a83..dddc60a 100644
--- a/src/demo/demo.h
+++ b/src/demo/demo.h
@@ -24,7 +24,7 @@ class Demo : public eng::Game {
void LostFocus() override;
- void GainedFocus() override;
+ void GainedFocus(bool from_interstitial_ad) override;
void AddScore(int score);
diff --git a/src/demo/enemy.cc b/src/demo/enemy.cc
index 0ef88f5..9706020 100644
--- a/src/demo/enemy.cc
+++ b/src/demo/enemy.cc
@@ -57,7 +57,7 @@ bool Enemy::Initialize() {
}
void Enemy::Update(float delta_time) {
- if (!waiting_for_next_wave_) {
+ if (!waiting_for_next_wave_ && !paused_) {
if (spawn_factor_interpolator_ < 1) {
spawn_factor_interpolator_ += delta_time * 0.1f;
if (spawn_factor_interpolator_ > 1)
@@ -70,17 +70,23 @@ void Enemy::Update(float delta_time) {
SpawnNextEnemy();
}
- for (auto it = enemies_.begin(); it != enemies_.end(); ++it) {
- if (it->marked_for_removal) {
+ for (auto it = enemies_.begin(); it != enemies_.end();) {
+ if (it->marked_for_removal)
it = enemies_.erase(it);
- continue;
- }
- it->sprite_animator.Update(delta_time);
- it->target_animator.Update(delta_time);
- it->blast_animator.Update(delta_time);
- it->health_animator.Update(delta_time);
- it->score_animator.Update(delta_time);
- it->movement_animator.Update(delta_time);
+ else
+ ++it;
+ }
+}
+
+void Enemy::Pause(bool pause) {
+ paused_ = pause;
+ for (auto& e : enemies_) {
+ e.movement_animator.PauseOrResumeAll(pause);
+ e.sprite_animator.PauseOrResumeAll(pause);
+ e.target_animator.PauseOrResumeAll(pause);
+ e.blast_animator.PauseOrResumeAll(pause);
+ e.health_animator.PauseOrResumeAll(pause);
+ e.score_animator.PauseOrResumeAll(pause);
}
}
@@ -95,8 +101,8 @@ Vector2 Enemy::GetTargetPos(DamageType damage_type) {
EnemyUnit* target = GetTarget(damage_type);
if (target)
- return target->sprite.GetOffset() -
- Vector2(0, target->sprite.GetScale().y / 2.5f);
+ return target->sprite.GetPosition() -
+ Vector2(0, target->sprite.GetSize().y / 2.5f);
return {0, 0};
}
@@ -122,11 +128,11 @@ void Enemy::SelectTarget(DamageType damage_type,
e.target_animator.Stop(Animator::kAllAnimations);
}
- if (!base::Intersection(e.sprite.GetOffset(),
- e.sprite.GetScale() * snap_factor, origin, dir))
+ if (!base::Intersection(e.sprite.GetPosition(),
+ e.sprite.GetSize() * snap_factor, origin, dir))
continue;
- Vector2 weapon_enemy_dir = e.sprite.GetOffset() - origin;
+ Vector2 weapon_enemy_dir = e.sprite.GetPosition() - origin;
float enemy_weapon_dist = weapon_enemy_dir.Magnitude();
if (closest_dist > enemy_weapon_dist) {
closest_dist = enemy_weapon_dist;
@@ -232,10 +238,10 @@ void Enemy::TakeDamage(EnemyUnit* target, int damage) {
} else {
target->targetted_by_weapon_ = kDamageType_Invalid;
- Vector2 s = target->sprite.GetScale() * Vector2(0.6f, 0.01f);
+ Vector2 s = target->sprite.GetSize() * Vector2(0.6f, 0.01f);
s.x *= (float)target->hit_points / (float)target->total_health;
- float t = (s.x - target->health_bar.GetScale().x) / 2;
- target->health_bar.SetScale(s);
+ float t = (s.x - target->health_bar.GetSize().x) / 2;
+ target->health_bar.SetSize(s);
target->health_bar.Translate({t, 0});
target->health_base.SetVisible(true);
@@ -312,8 +318,8 @@ void Enemy::Spawn(EnemyType enemy_type,
}
e.sprite.SetZOrder(11);
e.sprite.SetVisible(true);
- Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2);
- e.sprite.SetOffset(spawn_pos);
+ Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetSize().y / 2);
+ e.sprite.SetPosition(spawn_pos);
e.sprite.SetFrame(enemy_frame_start[enemy_type][damage_type]);
e.sprite_animator.SetFrames(enemy_frame_count[enemy_type][damage_type],
@@ -324,28 +330,28 @@ void Enemy::Spawn(EnemyType enemy_type,
e.target.Create("target_tex", {6, 2});
e.target.SetZOrder(12);
- e.target.SetOffset(spawn_pos);
+ e.target.SetPosition(spawn_pos);
e.blast.Create("blast_tex", {6, 2});
e.blast.SetZOrder(12);
- e.blast.SetOffset(spawn_pos);
+ e.blast.SetPosition(spawn_pos);
e.health_base.SetZOrder(11);
- e.health_base.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
- e.health_base.SetOffset(spawn_pos);
+ e.health_base.SetSize(e.sprite.GetSize() * Vector2(0.6f, 0.01f));
+ e.health_base.SetPosition(spawn_pos);
e.health_base.PlaceToBottomOf(e.sprite);
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1});
e.health_bar.SetZOrder(11);
- e.health_bar.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f));
- e.health_bar.SetOffset(spawn_pos);
+ e.health_bar.SetSize(e.sprite.GetSize() * Vector2(0.6f, 0.01f));
+ e.health_bar.SetPosition(spawn_pos);
e.health_bar.PlaceToBottomOf(e.sprite);
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1});
e.score.Create("score_tex"s + std::to_string(e.enemy_type));
e.score.SetZOrder(12);
e.score.SetColor({1, 1, 1, 1});
- e.score.SetOffset(spawn_pos);
+ e.score.SetPosition(spawn_pos);
e.target_animator.Attach(&e.target);
diff --git a/src/demo/enemy.h b/src/demo/enemy.h
index 1881440..96d0122 100644
--- a/src/demo/enemy.h
+++ b/src/demo/enemy.h
@@ -26,6 +26,8 @@ class Enemy {
void Update(float delta_time);
+ void Pause(bool pause);
+
bool HasTarget(DamageType damage_type);
base::Vector2 GetTargetPos(DamageType damage_type);
@@ -87,6 +89,8 @@ class Enemy {
int last_spawn_col_ = 0;
+ bool paused_ = false;
+
void TakeDamage(EnemyUnit* target, int damage);
void SpawnNextEnemy();
diff --git a/src/demo/hud.cc b/src/demo/hud.cc
index c5e90d3..8b41e9a 100644
--- a/src/demo/hud.cc
+++ b/src/demo/hud.cc
@@ -44,19 +44,19 @@ bool Hud::Initialize() {
text_[i].SetZOrder(30);
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0));
- Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetScale() / 2);
+ Vector2 pos = (engine.GetScreenSize() / 2 - text_[i].GetSize() / 2);
pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
Vector2 scale = engine.GetScreenSize() * Vector2(1, 0);
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 0);
- scale += text_[0].GetScale() * Vector2(0, 0.3f);
+ scale += text_[0].GetSize() * Vector2(0, 0.3f);
progress_bar_[i].SetZOrder(30);
- progress_bar_[i].Scale(scale);
+ progress_bar_[i].SetSize(scale);
progress_bar_[i].Translate(pos * Vector2(0, 1));
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0));
- pos -= progress_bar_[i].GetScale() * Vector2(0, 4);
+ pos -= progress_bar_[i].GetSize() * Vector2(0, 4);
text_[i].Translate(pos * Vector2(i ? 1 : -1, 1));
progress_bar_animator_[i].Attach(&progress_bar_[i]);
@@ -73,13 +73,6 @@ bool Hud::Initialize() {
return true;
}
-void Hud::Update(float delta_time) {
- for (int i = 0; i < 2; ++i) {
- text_animator_[i].Update(delta_time);
- progress_bar_animator_[i].Update(delta_time);
- }
-}
-
void Hud::Show() {
if (text_[0].IsVisible())
return;
@@ -118,9 +111,9 @@ void Hud::SetWave(int wave, bool flash) {
void Hud::SetProgress(float progress) {
progress = std::min(std::max(0.0f, progress), 1.0f);
last_progress_ = progress;
- Vector2 s = progress_bar_[0].GetScale() * Vector2(progress, 1);
- float t = (s.x - progress_bar_[1].GetScale().x) / 2;
- progress_bar_[1].SetScale(s);
+ Vector2 s = progress_bar_[0].GetSize() * Vector2(progress, 1);
+ float t = (s.x - progress_bar_[1].GetSize().x) / 2;
+ progress_bar_[1].SetSize(s);
progress_bar_[1].Translate({t, 0});
}
diff --git a/src/demo/hud.h b/src/demo/hud.h
index e713cf2..d425895 100644
--- a/src/demo/hud.h
+++ b/src/demo/hud.h
@@ -20,8 +20,6 @@ class Hud {
bool Initialize();
- void Update(float delta_time);
-
void Show();
void SetScore(int score, bool flash);
diff --git a/src/demo/menu.cc b/src/demo/menu.cc
index 0c0aece..e4a8b7b 100644
--- a/src/demo/menu.cc
+++ b/src/demo/menu.cc
@@ -75,19 +75,11 @@ bool Menu::Initialize() {
return true;
}
-void Menu::Update(float delta_time) {
- for (int i = 0; i < kOption_Max; ++i) {
- if (items_[i].hide)
- continue;
- items_[i].text_animator.Update(delta_time);
- }
-}
-
void Menu::OnInputEvent(std::unique_ptr event) {
if (event->GetType() == InputEvent::kDragStart)
- tap_pos_[0] = tap_pos_[1] = event->GetVector(0);
+ tap_pos_[0] = tap_pos_[1] = event->GetVector();
else if (event->GetType() == InputEvent::kDrag)
- tap_pos_[1] = event->GetVector(0);
+ tap_pos_[1] = event->GetVector();
if (event->GetType() != InputEvent::kDragEnd || IsAnimating())
return;
@@ -95,13 +87,11 @@ void Menu::OnInputEvent(std::unique_ptr event) {
for (int i = 0; i < kOption_Max; ++i) {
if (items_[i].hide)
continue;
- if (!Intersection(items_[i].text.GetOffset(),
- items_[i].text.GetScale() * Vector2(1.2f, 2),
- tap_pos_[0]))
+ if (!Intersection(items_[i].text.GetPosition(),
+ items_[i].text.GetSize() * Vector2(1.2f, 2), tap_pos_[0]))
continue;
- if (!Intersection(items_[i].text.GetOffset(),
- items_[i].text.GetScale() * Vector2(1.2f, 2),
- tap_pos_[1]))
+ if (!Intersection(items_[i].text.GetPosition(),
+ items_[i].text.GetSize() * Vector2(1.2f, 2), tap_pos_[1]))
continue;
items_[i].text_animator.SetEndCallback(Animator::kBlending,
@@ -117,12 +107,13 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
if (i == o)
items_[i].hide = !enable;
if (!items_[i].hide) {
- items_[i].text.SetOffset({0, 0});
+ items_[i].text.SetPosition({0, 0});
if (last >= 0) {
items_[i].text.PlaceToBottomOf(items_[last].text);
- items_[i].text.Translate(items_[last].text.GetOffset() * Vector2(0, 1));
+ items_[i].text.Translate(items_[last].text.GetPosition() *
+ Vector2(0, 1));
items_[i].text.Translate(
- {0, items_[last].text.GetScale().y * -kMenuOptionSpace});
+ {0, items_[last].text.GetSize().y * -kMenuOptionSpace});
}
if (first < 0)
first = i;
@@ -131,7 +122,8 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
}
float center_offset_y =
- (items_[first].text.GetOffset().y - items_[last].text.GetOffset().y) / 2;
+ (items_[first].text.GetPosition().y - items_[last].text.GetPosition().y) /
+ 2;
for (int i = 0; i < kOption_Max; ++i) {
if (!items_[i].hide)
items_[i].text.Translate({0, center_offset_y});
diff --git a/src/demo/menu.h b/src/demo/menu.h
index 46a7189..c7324d0 100644
--- a/src/demo/menu.h
+++ b/src/demo/menu.h
@@ -30,8 +30,6 @@ class Menu {
bool Initialize();
- void Update(float delta_time);
-
void OnInputEvent(std::unique_ptr event);
void SetOptionEnabled(Option o, bool enable);
diff --git a/src/demo/player.cc b/src/demo/player.cc
index cd8d77e..d601f70 100644
--- a/src/demo/player.cc
+++ b/src/demo/player.cc
@@ -31,24 +31,26 @@ bool Player::Initialize() {
}
void Player::Update(float delta_time) {
- for (int i = 0; i < 2; ++i) {
- warmup_animator_[i].Update(delta_time);
- cooldown_animator_[i].Update(delta_time);
- beam_animator_[i].Update(delta_time);
- spark_animator_[i].Update(delta_time);
- }
-
if (active_weapon_ != kDamageType_Invalid)
UpdateTarget();
}
+void Player::Pause(bool pause) {
+ for (int i = 0; i < 2; ++i) {
+ warmup_animator_[i].PauseOrResumeAll(pause);
+ cooldown_animator_[i].PauseOrResumeAll(pause);
+ beam_animator_[i].PauseOrResumeAll(pause);
+ spark_animator_[i].PauseOrResumeAll(pause);
+ }
+}
+
void Player::OnInputEvent(std::unique_ptr event) {
if (event->GetType() == InputEvent::kNavigateBack)
NavigateBack();
else if (event->GetType() == InputEvent::kDragStart)
- DragStart(event->GetVector(0));
+ DragStart(event->GetVector());
else if (event->GetType() == InputEvent::kDrag)
- Drag(event->GetVector(0));
+ Drag(event->GetVector());
else if (event->GetType() == InputEvent::kDragEnd)
DragEnd();
else if (event->GetType() == InputEvent::kDragCancel)
@@ -58,18 +60,18 @@ void Player::OnInputEvent(std::unique_ptr event) {
Vector2 Player::GetWeaponPos(DamageType type) const {
return Engine::Get().GetScreenSize() /
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) +
- Vector2(0, weapon_[type].GetScale().y * 0.7f);
+ Vector2(0, weapon_[type].GetSize().y * 0.7f);
}
Vector2 Player::GetWeaponScale() const {
- return weapon_[0].GetScale();
+ return weapon_[0].GetSize();
}
DamageType Player::GetWeaponType(const Vector2& pos) {
DamageType closest_weapon = kDamageType_Invalid;
float closest_dist = std::numeric_limits::max();
for (int i = 0; i < 2; ++i) {
- float dist = (pos - weapon_[i].GetOffset()).Magnitude();
+ float dist = (pos - weapon_[i].GetPosition()).Magnitude();
if (dist < closest_dist) {
closest_dist = dist;
closest_weapon = (DamageType)i;
@@ -77,20 +79,11 @@ DamageType Player::GetWeaponType(const Vector2& pos) {
}
DCHECK(closest_weapon != kDamageType_Invalid);
- if (closest_dist < weapon_[closest_weapon].GetScale().x * 0.9f)
+ if (closest_dist < weapon_[closest_weapon].GetSize().x * 0.9f)
return closest_weapon;
return kDamageType_Invalid;
}
-void Player::SetBeamLength(DamageType type, float len) {
- beam_[type].SetOffset({0, 0});
- beam_[type].SetScale({len, beam_[type].GetScale().y});
- beam_[type].PlaceToRightOf(weapon_[type]);
- beam_[type].Translate(weapon_[type].GetScale() * Vector2(-0.5f, 0));
- beam_[type].SetPivot(beam_[type].GetOffset());
- beam_[type].Translate(weapon_[type].GetOffset());
-}
-
void Player::WarmupWeapon(DamageType type) {
cooldown_animator_[type].Stop(Animator::kFrames);
warmup_animator_[type].Play(Animator::kFrames, false);
@@ -106,25 +99,27 @@ void Player::Fire(DamageType type, Vector2 dir) {
Enemy& enemy = static_cast(engine.GetGame())->GetEnemy();
if (enemy.HasTarget(type))
- dir = weapon_[type].GetOffset() - enemy.GetTargetPos(type);
+ dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type);
else
dir *= engine.GetScreenSize().y * 1.3f;
float len = dir.Magnitude();
- SetBeamLength(type, len);
+ beam_[type].SetSize({len, beam_[type].GetSize().y});
+ beam_[type].SetPosition(weapon_[type].GetPosition());
dir.Normalize();
float cos_theta = dir.DotProduct(Vector2(1, 0));
float theta = acos(cos_theta) + M_PI;
beam_[type].SetTheta(theta);
- beam_spark_[type].SetTheta(theta);
+ auto offset = beam_[type].GetRotation() * (len / 2);
+ beam_[type].Translate({offset.y, -offset.x});
beam_[type].SetColor({1, 1, 1, 1});
beam_[type].SetVisible(true);
beam_spark_[type].SetVisible(true);
spark_animator_[type].Stop(Animator::kMovement);
- float length = beam_[type].GetScale().x * 0.85f;
+ float length = beam_[type].GetSize().x * 0.9f;
Vector2 movement = dir * -length;
// Convert from units per second to duration.
float speed = 1.0f / (18.0f / length);
@@ -144,33 +139,27 @@ void Player::SetupWeapons() {
drag_sign_[i].SetZOrder(21);
drag_sign_[i].SetFrame(i * 8);
+ Vector2 pos = GetWeaponPos((DamageType)i);
+
// Setup weapon.
weapon_[i].Create("weapon_tex", {8, 2});
weapon_[i].SetZOrder(24);
weapon_[i].SetVisible(true);
weapon_[i].SetFrame(wepon_warmup_frame[i]);
+ weapon_[i].SetPosition(pos);
// Setup beam.
beam_[i].Create("beam_tex", {1, 2});
beam_[i].SetZOrder(22);
beam_[i].SetFrame(i);
- beam_[i].PlaceToRightOf(weapon_[i]);
- beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
- beam_[i].SetPivot(beam_[i].GetOffset());
+ beam_[i].SetPosition(pos);
+ beam_[i].Translate(beam_[i].GetSize() * Vector2(-0.5f, -0.5f));
// Setup beam spark.
beam_spark_[i].Create("weapon_tex", {8, 2});
beam_spark_[i].SetZOrder(23);
beam_spark_[i].SetFrame(i * 8 + 1);
- beam_spark_[i].PlaceToRightOf(weapon_[i]);
- beam_spark_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0));
- beam_spark_[i].SetPivot(beam_spark_[i].GetOffset());
-
- // Place parts on the screen.
- Vector2 offset = GetWeaponPos((DamageType)i);
- beam_[i].Translate(offset);
- beam_spark_[i].Translate(offset);
- weapon_[i].Translate(offset);
+ beam_spark_[i].SetPosition(pos);
// Setup animators.
weapon_[i].SetFrame(wepon_cooldown_frame[i]);
@@ -227,7 +216,7 @@ void Player::DragStart(const Vector2& pos) {
drag_start_ = drag_end_ = pos;
- drag_sign_[active_weapon_].SetOffset(drag_start_);
+ drag_sign_[active_weapon_].SetPosition(drag_start_);
drag_sign_[active_weapon_].SetVisible(true);
}
@@ -236,7 +225,7 @@ void Player::Drag(const Vector2& pos) {
return;
drag_end_ = pos;
- drag_sign_[active_weapon_].SetOffset(drag_end_);
+ drag_sign_[active_weapon_].SetPosition(drag_end_);
if (ValidateDrag()) {
if (!drag_valid_ && !IsFiring(active_weapon_))
@@ -307,7 +296,7 @@ bool Player::ValidateDrag() {
Vector2 dir = drag_end_ - drag_start_;
float len = dir.Magnitude();
dir.Normalize();
- if (len < weapon_[active_weapon_].GetScale().y / 4)
+ if (len < weapon_[active_weapon_].GetSize().y / 4)
return false;
if (dir.DotProduct(Vector2(0, 1)) < 0)
return false;
diff --git a/src/demo/player.h b/src/demo/player.h
index c34bd01..e883bb9 100644
--- a/src/demo/player.h
+++ b/src/demo/player.h
@@ -21,6 +21,8 @@ class Player {
void Update(float delta_time);
+ void Pause(bool pause);
+
void OnInputEvent(std::unique_ptr event);
base::Vector2 GetWeaponPos(DamageType type) const;
@@ -45,8 +47,6 @@ class Player {
DamageType GetWeaponType(const base::Vector2& pos);
- void SetBeamLength(DamageType type, float len);
-
void WarmupWeapon(DamageType type);
void CooldownWeapon(DamageType type);
diff --git a/src/demo/sky_quad.cc b/src/demo/sky_quad.cc
index d6b926f..cc4b0b1 100644
--- a/src/demo/sky_quad.cc
+++ b/src/demo/sky_quad.cc
@@ -15,6 +15,7 @@ SkyQuad::SkyQuad()
: shader_(Engine::Get().CreateRenderResource()),
sky_offset_{
0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().GetFloat())} {
+ last_sky_offset_ = sky_offset_;
}
SkyQuad::~SkyQuad() = default;
@@ -25,7 +26,8 @@ bool SkyQuad::Create() {
auto source = std::make_unique();
if (!source->Load("sky.glsl"))
return false;
- shader_->Create(std::move(source), engine.GetQuad()->vertex_description());
+ shader_->Create(std::move(source), engine.GetQuad()->vertex_description(),
+ Engine::Get().GetQuad()->primitive());
scale_ = engine.GetScreenSize();
@@ -37,8 +39,8 @@ bool SkyQuad::Create() {
}
void SkyQuad::Update(float delta_time) {
- sky_offset_ += {0, delta_time * 0.04f};
- color_animator_.Update(delta_time);
+ last_sky_offset_ = sky_offset_;
+ sky_offset_ += {0, delta_time * speed_};
}
void SkyQuad::Draw(float frame_frac) {
@@ -46,13 +48,13 @@ void SkyQuad::Draw(float frame_frac) {
shader_->Activate();
shader_->SetUniform("scale", scale_);
- shader_->SetUniform("projection", Engine::Get().GetProjectionMarix());
+ shader_->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader_->SetUniform("sky_offset", sky_offset);
shader_->SetUniform("nebula_color",
{nebula_color_.x, nebula_color_.y, nebula_color_.z});
+ shader_->UploadUniforms();
Engine::Get().GetQuad()->Draw();
- last_sky_offset_ = sky_offset_;
}
void SkyQuad::ContextLost() {
@@ -64,3 +66,7 @@ void SkyQuad::SwitchColor(const Vector4& color) {
std::bind(SmoothStep, std::placeholders::_1));
color_animator_.Play(Animator::kBlending, false);
}
+
+void SkyQuad::SetSpeed(float speed) {
+ speed_ = speed;
+}
diff --git a/src/demo/sky_quad.h b/src/demo/sky_quad.h
index 72cac08..db1c9f4 100644
--- a/src/demo/sky_quad.h
+++ b/src/demo/sky_quad.h
@@ -40,6 +40,8 @@ class SkyQuad : public eng::Animatable {
void SwitchColor(const base::Vector4& color);
+ void SetSpeed(float speed);
+
private:
std::unique_ptr shader_;
@@ -49,6 +51,8 @@ class SkyQuad : public eng::Animatable {
base::Vector2 scale_ = {1, 1};
eng::Animator color_animator_;
+
+ float speed_ = 0;
};
#endif // SKY_QUAD_H
diff --git a/src/engine/BUILD.gn b/src/engine/BUILD.gn
index e9fd64e..2f8474e 100644
--- a/src/engine/BUILD.gn
+++ b/src/engine/BUILD.gn
@@ -27,6 +27,8 @@ source_set("engine") {
"input_event.h",
"mesh.cc",
"mesh.h",
+ "persistent_data.cc",
+ "persistent_data.h",
"platform/asset_file.cc",
"platform/asset_file.h",
"platform/platform_base.cc",
@@ -35,14 +37,15 @@ source_set("engine") {
"platform/platform.h",
"renderer/geometry.cc",
"renderer/geometry.h",
- "renderer/opengl.h",
- "renderer/render_command.cc",
- "renderer/render_command.h",
+ "renderer/opengl/opengl.h",
+ "renderer/opengl/render_command.cc",
+ "renderer/opengl/render_command.h",
+ "renderer/opengl/renderer_opengl.cc",
+ "renderer/opengl/renderer_opengl.h",
"renderer/render_resource.cc",
"renderer/render_resource.h",
"renderer/renderer_types.cc",
"renderer/renderer_types.h",
- "renderer/renderer.cc",
"renderer/renderer.h",
"renderer/shader.cc",
"renderer/shader.h",
@@ -68,7 +71,7 @@ source_set("engine") {
"platform/asset_file_linux.cc",
"platform/platform_linux.cc",
"platform/platform_linux.h",
- "renderer/renderer_linux.cc",
+ "renderer/opengl/renderer_opengl_linux.cc",
]
#ldflags += ["-L/usr/X11R6/lib"]
diff --git a/src/engine/animatable.cc b/src/engine/animatable.cc
index 4f25921..90cd50a 100644
--- a/src/engine/animatable.cc
+++ b/src/engine/animatable.cc
@@ -6,16 +6,16 @@ using namespace base;
namespace eng {
-void Animatable::Translate(const Vector2& offset) {
- offset_ += offset;
+void Animatable::Translate(const Vector2& pos) {
+ position_ += pos;
}
void Animatable::Scale(const Vector2& scale) {
- scale_ *= scale;
+ scale_ = scale;
}
void Animatable::Scale(float scale) {
- scale_ *= scale;
+ scale_ = {scale, scale};
}
void Animatable::Rotate(float angle) {
diff --git a/src/engine/animatable.h b/src/engine/animatable.h
index 1bf070b..b9d2858 100644
--- a/src/engine/animatable.h
+++ b/src/engine/animatable.h
@@ -11,20 +11,19 @@ class Animatable : public Drawable {
Animatable() = default;
~Animatable() override = default;
- void Translate(const base::Vector2& offset);
+ void Translate(const base::Vector2& pos);
void Scale(const base::Vector2& scale);
void Scale(float scale);
void Rotate(float angle);
- void SetOffset(const base::Vector2& offset) { offset_ = offset; }
- void SetScale(const base::Vector2& scale) { scale_ = scale; }
- void SetPivot(const base::Vector2& pivot) { pivot_ = pivot; }
+ void SetPosition(const base::Vector2& pos) { position_ = pos; }
+ void SetSize(const base::Vector2& size) { size_ = size; }
void SetTheta(float theta);
- base::Vector2 GetOffset() const { return offset_; }
- base::Vector2 GetScale() const { return scale_; }
- base::Vector2 GetPivot() const { return pivot_; }
+ base::Vector2 GetPosition() const { return position_; }
+ base::Vector2 GetSize() const { return size_ * scale_; }
float GetTheta() const { return theta_; }
+ base::Vector2 GetRotation() const { return rotation_; }
// Pure virtuals for frame animation support.
virtual void SetFrame(size_t frame) = 0;
@@ -35,25 +34,25 @@ class Animatable : public Drawable {
virtual base::Vector4 GetColor() const = 0;
void PlaceToLeftOf(const Animatable& s) {
- Translate({s.GetScale().x / -2.0f + GetScale().x / -2.0f, 0});
+ Translate({s.GetSize().x / -2.0f + GetSize().x / -2.0f, 0});
}
void PlaceToRightOf(const Animatable& s) {
- Translate({s.GetScale().x / 2.0f + GetScale().x / 2.0f, 0});
+ Translate({s.GetSize().x / 2.0f + GetSize().x / 2.0f, 0});
}
void PlaceToTopOf(const Animatable& s) {
- Translate({0, s.GetScale().y / 2.0f + GetScale().y / 2.0f});
+ Translate({0, s.GetSize().y / 2.0f + GetSize().y / 2.0f});
}
void PlaceToBottomOf(const Animatable& s) {
- Translate({0, s.GetScale().y / -2.0f + GetScale().y / -2.0f});
+ Translate({0, s.GetSize().y / -2.0f + GetSize().y / -2.0f});
}
protected:
- base::Vector2 offset_ = {0, 0};
+ base::Vector2 position_ = {0, 0};
+ base::Vector2 size_ = {1, 1};
base::Vector2 scale_ = {1, 1};
- base::Vector2 pivot_ = {0, 0};
base::Vector2 rotation_ = {0, 1};
float theta_ = 0;
};
diff --git a/src/engine/animator.cc b/src/engine/animator.cc
index 4307e36..121a9e3 100644
--- a/src/engine/animator.cc
+++ b/src/engine/animator.cc
@@ -3,11 +3,20 @@
#include "../base/interpolation.h"
#include "../base/log.h"
#include "animatable.h"
+#include "engine.h"
using namespace base;
namespace eng {
+Animator::Animator() {
+ Engine::Get().AddAnimator(this);
+}
+
+Animator::~Animator() {
+ Engine::Get().RemoveAnimator(this);
+}
+
void Animator::Attach(Animatable* animatable) {
elements_.push_back({animatable,
{0, 0},
@@ -18,7 +27,10 @@ void Animator::Attach(Animatable* animatable) {
void Animator::Play(int animation, bool loop) {
play_flags_ |= animation;
- loop_flags_ |= loop ? animation : 0;
+ if (loop)
+ loop_flags_ |= animation;
+ else
+ loop_flags_ &= ~animation;
}
void Animator::Pause(int animation) {
@@ -43,6 +55,16 @@ void Animator::Stop(int animation) {
loop_flags_ &= ~animation;
}
+void Animator::PauseOrResumeAll(bool pause) {
+ if (pause) {
+ resume_flags_ = play_flags_;
+ play_flags_ = 0;
+ } else {
+ play_flags_ = resume_flags_;
+ resume_flags_ = 0;
+ }
+}
+
float Animator::GetTime(int animation) {
if ((animation & kMovement) != 0)
return movement_time_;
@@ -55,7 +77,7 @@ float Animator::GetTime(int animation) {
return timer_time_;
}
-void Animator::SetTime(int animation, float time) {
+void Animator::SetTime(int animation, float time, bool force_update) {
DCHECK(time >= 0 && time <= 1);
if ((animation & kMovement) != 0)
@@ -68,6 +90,13 @@ void Animator::SetTime(int animation, float time) {
frame_time_ = time;
if ((animation & kTimer) != 0)
timer_time_ = time;
+
+ if (force_update) {
+ unsigned play_flags = play_flags_;
+ play_flags_ = animation;
+ Update(0);
+ play_flags_ = play_flags;
+ }
}
void Animator::SetEndCallback(int animation, base::Closure cb) {
@@ -95,7 +124,7 @@ void Animator::SetMovement(Vector2 direction,
movement_interpolator_ = std::move(interpolator);
for (auto& a : elements_)
- a.movement_last_offset = {0, 0};
+ a.movement_last_pos = {0, 0};
}
void Animator::SetRotation(float trget,
@@ -149,39 +178,41 @@ void Animator::Update(float delta_time) {
UpdateFrame(delta_time);
if (play_flags_ & kTimer)
UpdateTimer(delta_time);
+}
+void Animator::EvalAnim(float frame_time) {
for (auto& a : elements_) {
if (play_flags_ & kMovement) {
- float interpolated_time = movement_interpolator_
- ? movement_interpolator_(movement_time_)
- : movement_time_;
- Vector2 offset =
- base::Lerp({0, 0}, movement_direction_, interpolated_time);
- a.animatable->Translate(offset - a.movement_last_offset);
- a.movement_last_offset = offset;
+ float time = movement_time_ + movement_speed_ * frame_time;
+ float interpolated_time =
+ movement_interpolator_ ? movement_interpolator_(time) : time;
+ Vector2 pos = base::Lerp({0, 0}, movement_direction_, interpolated_time);
+ a.animatable->Translate(pos - a.movement_last_pos);
+ a.movement_last_pos = pos;
}
if (play_flags_ & kRotation) {
- float interpolated_time = rotation_interpolator_
- ? rotation_interpolator_(rotation_time_)
- : rotation_time_;
+ float time = rotation_time_ + rotation_speed_ * frame_time;
+ float interpolated_time =
+ rotation_interpolator_ ? rotation_interpolator_(time) : time;
float theta = base::Lerp(0.0f, rotation_target_, interpolated_time);
a.animatable->Rotate(theta - a.rotation_last_theta);
a.rotation_last_theta = theta;
}
if (play_flags_ & kBlending) {
- float interpolated_time = blending_interpolator_
- ? blending_interpolator_(blending_time_)
- : blending_time_;
+ float time = blending_time_ + blending_speed_ * frame_time;
+ float interpolated_time =
+ blending_interpolator_ ? blending_interpolator_(time) : time;
Vector4 r =
base::Lerp(a.blending_start, blending_target_, interpolated_time);
a.animatable->SetColor(r);
}
if (play_flags_ & kFrames) {
+ float time = frame_time_ + frame_speed_ * frame_time;
float interpolated_time =
- frame_interpolator_ ? frame_interpolator_(frame_time_) : frame_time_;
+ frame_interpolator_ ? frame_interpolator_(time) : time;
int target = a.frame_start_ + frame_count_;
int r = base::Lerp(a.frame_start_, target, interpolated_time);
if (r < target)
diff --git a/src/engine/animator.h b/src/engine/animator.h
index 8d0e3a4..cef3772 100644
--- a/src/engine/animator.h
+++ b/src/engine/animator.h
@@ -25,8 +25,8 @@ class Animator {
using Interpolator = std::function;
- Animator() = default;
- ~Animator() = default;
+ Animator();
+ ~Animator();
void Attach(Animatable* animatable);
@@ -34,9 +34,11 @@ class Animator {
void Pause(int animation);
void Stop(int animation);
+ void PauseOrResumeAll(bool pause);
+
// Get/set current time of the given animation.
float GetTime(int animation);
- void SetTime(int animation, float time);
+ void SetTime(int animation, float time, bool force_update = false);
// Set callback ro be called once animation ends.
void SetEndCallback(int animation, base::Closure cb);
@@ -67,13 +69,14 @@ class Animator {
void SetVisible(bool visible);
void Update(float delta_time);
+ void EvalAnim(float frame_time);
bool IsPlaying(int animation) const { return play_flags_ & animation; }
private:
struct Element {
Animatable* animatable;
- base::Vector2 movement_last_offset = {0, 0};
+ base::Vector2 movement_last_pos = {0, 0};
float rotation_last_theta = 0;
base::Vector4 blending_start = {0, 0, 0, 0};
int frame_start_ = 0;
@@ -81,6 +84,7 @@ class Animator {
unsigned int play_flags_ = 0;
unsigned int loop_flags_ = 0;
+ unsigned int resume_flags_ = 0;
std::vector elements_;
base::Vector2 movement_direction_ = {0, 0};
diff --git a/src/engine/audio/audio_alsa.cc b/src/engine/audio/audio_alsa.cc
index a56fddf..ba3e1d1 100644
--- a/src/engine/audio/audio_alsa.cc
+++ b/src/engine/audio/audio_alsa.cc
@@ -157,11 +157,11 @@ void AudioAlsa::Resume() {
suspend_audio_thread_.store(false, std::memory_order_relaxed);
}
-size_t AudioAlsa::GetSampleRate() {
+int AudioAlsa::GetHardwareSampleRate() {
return sample_rate_;
}
-bool AudioAlsa::StartAudioThread() {
+void AudioAlsa::StartAudioThread() {
LOG << "Starting audio thread.";
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed));
@@ -189,7 +189,8 @@ void AudioAlsa::AudioThreadMain() {
while (suspend_audio_thread_.load(std::memory_order_relaxed)) {
if (terminate_audio_thread_.load(std::memory_order_relaxed))
return;
- std::this_thread::yield();
+ // Avoid busy-looping.
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
RenderAudio(buffer.get(), num_frames);
diff --git a/src/engine/audio/audio_alsa.h b/src/engine/audio/audio_alsa.h
index bb20d83..adf8c39 100644
--- a/src/engine/audio/audio_alsa.h
+++ b/src/engine/audio/audio_alsa.h
@@ -24,21 +24,21 @@ class AudioAlsa : public AudioBase {
void Suspend();
void Resume();
- size_t GetSampleRate();
+ int GetHardwareSampleRate();
private:
// Handle for the PCM device.
snd_pcm_t* device_;
std::thread audio_thread_;
- std::atomic terminate_audio_thread_ = false;
- std::atomic suspend_audio_thread_ = false;
+ std::atomic terminate_audio_thread_{false};
+ std::atomic suspend_audio_thread_{false};
size_t num_channels_ = 0;
- size_t sample_rate_ = 0;
+ int sample_rate_ = 0;
size_t period_size_ = 0;
- bool StartAudioThread();
+ void StartAudioThread();
void TerminateAudioThread();
void AudioThreadMain();
diff --git a/src/engine/audio/audio_base.cc b/src/engine/audio/audio_base.cc
index 1e24aa8..39f54b7 100644
--- a/src/engine/audio/audio_base.cc
+++ b/src/engine/audio/audio_base.cc
@@ -18,17 +18,19 @@ AudioBase::~AudioBase() = default;
void AudioBase::Play(std::shared_ptr sample) {
if (audio_enabled_) {
- std::lock_guard scoped_lock(lock_);
+ std::lock_guard scoped_lock(lock_);
samples_[0].push_back(sample);
- } else {
+ } else if ((sample->flags.load(std::memory_order_relaxed) &
+ AudioSample::kStopped) == 0) {
sample->active = false;
}
}
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
{
- std::lock_guard scoped_lock(lock_);
- samples_[1].splice(samples_[1].end(), samples_[0]);
+ std::unique_lock scoped_lock(lock_, std::try_to_lock);
+ if (scoped_lock)
+ samples_[1].splice(samples_[1].end(), samples_[0]);
}
memset(output_buffer, 0, sizeof(float) * num_frames * kChannelCount);
@@ -58,7 +60,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
size_t channel_offset =
(flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound()
- ? sound->hz() / 10
+ ? sound->sample_rate() / 10
: 0;
DCHECK(num_samples || sound->is_streaming_sound());
@@ -88,8 +90,8 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
// Basic resampling for variations.
accumulator += step;
- src_index += accumulator / 10;
- accumulator %= 10;
+ src_index += accumulator / 100;
+ accumulator %= 100;
}
// Advance source index.
@@ -122,9 +124,9 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
src[1] = src[0]; // mono.
num_samples = sound->GetNumSamples();
- Worker::Get().EnqueueTask(HERE,
- std::bind(&AudioBase::DoStream, this, *it,
- flags & AudioSample::kLoop));
+ Worker::Get().PostTask(HERE,
+ std::bind(&AudioBase::DoStream, this, *it,
+ flags & AudioSample::kLoop));
} else if (num_samples) {
DLOG << "Buffer underrun!";
src_index %= num_samples;
@@ -141,7 +143,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
(!sound->is_streaming_sound() ||
!sample->streaming_in_progress.load(std::memory_order_relaxed))) {
sample->marked_for_removal = false;
- main_thread_task_runner_->EnqueueTask(
+ main_thread_task_runner_->PostTask(
HERE, std::bind(&AudioBase::EndCallback, this, *it));
it = samples_[1].erase(it);
} else {
@@ -159,12 +161,10 @@ void AudioBase::DoStream(std::shared_ptr sample, bool loop) {
}
void AudioBase::EndCallback(std::shared_ptr sample) {
- AudioSample* s = sample.get();
+ sample->active = false;
- s->active = false;
-
- if (s->end_cb)
- s->end_cb();
+ if (sample->end_cb)
+ sample->end_cb();
}
} // namespace eng
diff --git a/src/engine/audio/audio_base.h b/src/engine/audio/audio_base.h
index f184546..0fc036d 100644
--- a/src/engine/audio/audio_base.h
+++ b/src/engine/audio/audio_base.h
@@ -3,9 +3,9 @@
#include
#include
+#include
#include "../../base/closure.h"
-#include "../../base/spinlock.h"
#include "audio_sample.h"
namespace base {
@@ -32,7 +32,7 @@ class AudioBase {
private:
std::list> samples_[2];
- base::Spinlock lock_;
+ std::mutex lock_;
base::TaskRunner* main_thread_task_runner_;
diff --git a/src/engine/audio/audio_oboe.cc b/src/engine/audio/audio_oboe.cc
index 27e19d0..2ad52d1 100644
--- a/src/engine/audio/audio_oboe.cc
+++ b/src/engine/audio/audio_oboe.cc
@@ -25,14 +25,14 @@ void AudioOboe::Shutdown() {
}
void AudioOboe::Suspend() {
- stream_->pause();
+ stream_->stop();
}
void AudioOboe::Resume() {
- stream_->start();
+ RestartStream();
}
-size_t AudioOboe::GetSampleRate() {
+int AudioOboe::GetHardwareSampleRate() {
return stream_->getSampleRate();
}
diff --git a/src/engine/audio/audio_oboe.h b/src/engine/audio/audio_oboe.h
index f30564e..7bdecfa 100644
--- a/src/engine/audio/audio_oboe.h
+++ b/src/engine/audio/audio_oboe.h
@@ -23,7 +23,7 @@ class AudioOboe : public AudioBase {
void Suspend();
void Resume();
- size_t GetSampleRate();
+ int GetHardwareSampleRate();
private:
class StreamCallback : public oboe::AudioStreamCallback {
diff --git a/src/engine/audio/audio_resource.cc b/src/engine/audio/audio_resource.cc
index 418438c..71f8175 100644
--- a/src/engine/audio/audio_resource.cc
+++ b/src/engine/audio/audio_resource.cc
@@ -61,7 +61,7 @@ void AudioResource::SetLoop(bool loop) {
if (loop)
sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed);
else
- sample_->flags.fetch_and(AudioSample::kLoop, std::memory_order_relaxed);
+ sample_->flags.fetch_and(~AudioSample::kLoop, std::memory_order_relaxed);
}
void AudioResource::SetSimulateStereo(bool simulate) {
@@ -69,12 +69,12 @@ void AudioResource::SetSimulateStereo(bool simulate) {
sample_->flags.fetch_or(AudioSample::kSimulateStereo,
std::memory_order_relaxed);
else
- sample_->flags.fetch_and(AudioSample::kSimulateStereo,
+ sample_->flags.fetch_and(~AudioSample::kSimulateStereo,
std::memory_order_relaxed);
}
void AudioResource::SetResampleStep(size_t step) {
- sample_->step.store(step + 10, std::memory_order_relaxed);
+ sample_->step.store(step + 100, std::memory_order_relaxed);
}
void AudioResource::SetMaxAmplitude(float max_amplitude) {
diff --git a/src/engine/audio/audio_sample.h b/src/engine/audio/audio_sample.h
index bbfb3c3..97035d6 100644
--- a/src/engine/audio/audio_sample.h
+++ b/src/engine/audio/audio_sample.h
@@ -28,7 +28,7 @@ struct AudioSample {
// Write accessed by main thread, read-only accessed by audio thread.
std::atomic flags{0};
- std::atomic step{10};
+ std::atomic step{100};
std::atomic amplitude_inc{0};
std::atomic max_amplitude{1.0f};
diff --git a/src/engine/engine.cc b/src/engine/engine.cc
index 5f80358..dbadeed 100644
--- a/src/engine/engine.cc
+++ b/src/engine/engine.cc
@@ -15,7 +15,6 @@
#include "mesh.h"
#include "platform/platform.h"
#include "renderer/geometry.h"
-#include "renderer/render_command.h"
#include "renderer/renderer.h"
#include "renderer/shader.h"
#include "renderer/texture.h"
@@ -68,6 +67,8 @@ bool Engine::Initialize() {
projection_ = base::Ortho(-1.0, 1.0, -aspect_ratio, aspect_ratio);
}
+ LOG << "image scale factor: " << GetImageScaleFactor();
+
if (renderer_->SupportsDXT5()) {
tex_comp_alpha_ = TextureCompressor::Create(TextureCompressor::kFormatDXT5);
} else if (renderer_->SupportsATC()) {
@@ -95,7 +96,7 @@ bool Engine::Initialize() {
game_ = GameFactoryBase::CreateGame("");
if (!game_) {
- printf("No game found to run.\n");
+ LOG << "No game found to run.";
return false;
}
@@ -113,6 +114,10 @@ void Engine::Shutdown() {
void Engine::Update(float delta_time) {
seconds_accumulated_ += delta_time;
+ ++tick_;
+
+ for (auto d : animators_)
+ d->Update(delta_time);
game_->Update(delta_time);
@@ -135,15 +140,20 @@ void Engine::Update(float delta_time) {
}
void Engine::Draw(float frame_frac) {
+ for (auto d : animators_)
+ d->EvalAnim(time_step_ * frame_frac);
+
drawables_.sort(
[](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); });
+ renderer_->PrepareForDrawing();
+
for (auto d : drawables_) {
if (d->IsVisible())
d->Draw(frame_frac);
}
- renderer_->EnqueueCommand(std::make_unique());
+ renderer_->Present();
}
void Engine::LostFocus() {
@@ -153,11 +163,11 @@ void Engine::LostFocus() {
game_->LostFocus();
}
-void Engine::GainedFocus() {
+void Engine::GainedFocus(bool from_interstitial_ad) {
audio_->Resume();
if (game_)
- game_->GainedFocus();
+ game_->GainedFocus(from_interstitial_ad);
}
void Engine::AddDrawable(Drawable* drawable) {
@@ -174,6 +184,20 @@ void Engine::RemoveDrawable(Drawable* drawable) {
}
}
+void Engine::AddAnimator(Animator* animator) {
+ DCHECK(std::find(animators_.begin(), animators_.end(), animator) ==
+ animators_.end());
+ animators_.push_back(animator);
+}
+
+void Engine::RemoveAnimator(Animator* animator) {
+ auto it = std::find(animators_.begin(), animators_.end(), animator);
+ if (it != animators_.end()) {
+ animators_.erase(it);
+ return;
+ }
+}
+
void Engine::Exit() {
platform_->Exit();
}
@@ -190,27 +214,16 @@ Vector2 Engine::ToPosition(const Vector2& vec) {
void Engine::SetImageSource(const std::string& asset_name,
const std::string& file_name,
bool persistent) {
- std::shared_ptr texture;
- auto it = textures_.find(asset_name);
- if (it != textures_.end()) {
- texture = it->second.texture;
- it->second.asset_file = file_name;
- it->second.create_image = nullptr;
- it->second.persistent = persistent;
- } else {
- texture = CreateRenderResource();
- textures_[asset_name] = {texture, file_name, nullptr, persistent};
- }
-
- if (persistent) {
- auto image = std::make_unique();
- if (image->Load(file_name)) {
- image->Compress();
- texture->Update(std::move(image));
- } else {
- texture->Destroy();
- }
- }
+ SetImageSource(
+ asset_name,
+ [file_name]() -> std::unique_ptr {
+ auto image = std::make_unique();
+ if (!image->Load(file_name))
+ return nullptr;
+ image->Compress();
+ return image;
+ },
+ persistent);
}
void Engine::SetImageSource(const std::string& asset_name,
@@ -221,11 +234,10 @@ void Engine::SetImageSource(const std::string& asset_name,
if (it != textures_.end()) {
texture = it->second.texture;
it->second.create_image = create_image;
- it->second.asset_file.clear();
it->second.persistent = persistent;
} else {
texture = CreateRenderResource();
- textures_[asset_name] = {texture, "", create_image, persistent};
+ textures_[asset_name] = {texture, create_image, persistent};
}
if (persistent) {
@@ -242,17 +254,7 @@ void Engine::RefreshImage(const std::string& asset_name) {
if (it == textures_.end())
return;
- std::unique_ptr image;
- if (!it->second.asset_file.empty()) {
- image = std::make_unique();
- if (image->Load(it->second.asset_file))
- image->Compress();
- else
- image.reset();
- } else if (it->second.create_image) {
- image = it->second.create_image();
- }
-
+ auto image = it->second.create_image();
if (image)
it->second.texture->Update(std::move(image));
else
@@ -278,9 +280,12 @@ std::unique_ptr Engine::CreateAudioResource() {
}
void Engine::AddInputEvent(std::unique_ptr event) {
+ if (replaying_)
+ return;
+
switch (event->GetType()) {
case InputEvent::kDragEnd:
- if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <=
+ if (((GetScreenSize() / 2) * 0.9f - event->GetVector()).Magnitude() <=
0.25f) {
SetSatsVisible(!stats_->IsVisible());
// TODO: Enqueue DragCancel so we can consume this event.
@@ -295,9 +300,9 @@ void Engine::AddInputEvent(std::unique_ptr event) {
break;
case InputEvent::kDrag:
if (stats_->IsVisible()) {
- if ((stats_->GetOffset() - event->GetVector(0)).Magnitude() <=
- stats_->GetScale().y)
- stats_->SetOffset(event->GetVector(0));
+ if ((stats_->GetPosition() - event->GetVector()).Magnitude() <=
+ stats_->GetSize().y)
+ stats_->SetPosition(event->GetVector());
// TODO: Enqueue DragCancel so we can consume this event.
}
break;
@@ -310,18 +315,91 @@ void Engine::AddInputEvent(std::unique_ptr event) {
std::unique_ptr Engine::GetNextInputEvent() {
std::unique_ptr event;
+
+ if (replaying_) {
+ if (replay_index_ < replay_data_.root()["input"].size()) {
+ auto data = replay_data_.root()["input"][replay_index_];
+ if (data["tick"].asUInt64() == tick_) {
+ event = std::make_unique(
+ (InputEvent::Type)data["input_type"].asInt(),
+ (size_t)data["pointer_id"].asUInt(),
+ Vector2(data["pos_x"].asFloat(), data["pos_y"].asFloat()));
+ ++replay_index_;
+ }
+ return event;
+ }
+ replaying_ = false;
+ replay_data_.root().clear();
+ }
+
if (!input_queue_.empty()) {
event.swap(input_queue_.front());
input_queue_.pop_front();
+
+ if (recording_) {
+ Json::Value data;
+ data["tick"] = tick_;
+ data["input_type"] = event->GetType();
+ data["pointer_id"] = event->GetPointerId();
+ data["pos_x"] = event->GetVector().x;
+ data["pos_y"] = event->GetVector().y;
+ replay_data_.root()["input"].append(data);
+ }
}
+
return event;
}
+void Engine::StartRecording(const Json::Value& payload) {
+ if (!replaying_ && !recording_) {
+ recording_ = true;
+ random_ = Random();
+ replay_data_.root()["seed"] = random_.seed();
+ replay_data_.root()["payload"] = payload;
+ tick_ = 0;
+ }
+}
+
+void Engine::EndRecording(const std::string file_name) {
+ if (recording_) {
+ DCHECK(!replaying_);
+
+ recording_ = false;
+ replay_data_.SaveAs(file_name, PersistentData::kShared);
+ replay_data_.root().clear();
+ }
+}
+
+bool Engine::Replay(const std::string file_name, Json::Value& payload) {
+ if (!replaying_ && !recording_ &&
+ replay_data_.Load(file_name, PersistentData::kShared)) {
+ replaying_ = true;
+ random_ = Random(replay_data_.root()["seed"].asUInt());
+ payload = replay_data_.root()["payload"];
+ tick_ = 0;
+ replay_index_ = 0;
+ }
+
+ return replaying_;
+}
+
void Engine::Vibrate(int duration) {
if (vibration_enabled_)
platform_->Vibrate(duration);
}
+void Engine::ShowInterstitialAd() {
+ platform_->ShowInterstitialAd();
+}
+
+void Engine::ShareFile(const std::string& file_name) {
+ platform_->ShareFile(file_name);
+}
+
+void Engine::SetKeepScreenOn(bool keep_screen_on) {
+ platform_->SetKeepScreenOn(keep_screen_on);
+}
+
void Engine::SetEnableAudio(bool enable) {
audio_->SetEnableAudio(enable);
}
@@ -342,12 +420,26 @@ int Engine::GetDeviceDpi() const {
return platform_->GetDeviceDpi();
}
+float Engine::GetImageScaleFactor() const {
+ float width_inch = static_cast(renderer_->screen_width()) /
+ static_cast(platform_->GetDeviceDpi());
+ return 2.57143f / width_inch;
+}
+
const std::string& Engine::GetRootPath() const {
return platform_->GetRootPath();
}
-size_t Engine::GetAudioSampleRate() {
- return audio_->GetSampleRate();
+const std::string& Engine::GetDataPath() const {
+ return platform_->GetDataPath();
+}
+
+const std::string& Engine::GetSharedDataPath() const {
+ return platform_->GetSharedDataPath();
+}
+
+int Engine::GetAudioHardwareSampleRate() {
+ return audio_->GetHardwareSampleRate();
}
bool Engine::IsMobile() const {
@@ -369,12 +461,16 @@ void Engine::ContextLost() {
}
bool Engine::CreateRenderResources() {
+ // This creates a normalized unit sized quad.
+ static const char vertex_description[] = "p2f;t2f";
+ static const float vertices[] = {
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 1.0f, 1.0f,
+ -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 1.0f, 0.0f,
+ };
+
// Create the quad geometry we can reuse for all sprites.
auto quad_mesh = std::make_unique();
- if (!quad_mesh->Load("engine/quad.mesh")) {
- LOG << "Could not create quad mesh.";
- return false;
- }
+ quad_mesh->Create(kPrimitive_TriangleStrip, vertex_description, 4, vertices);
quad_->Create(std::move(quad_mesh));
// Create the shader we can reuse for texture rendering.
@@ -383,7 +479,8 @@ bool Engine::CreateRenderResources() {
LOG << "Could not create pass through shader.";
return false;
}
- pass_through_shader_->Create(std::move(source), quad_->vertex_description());
+ pass_through_shader_->Create(std::move(source), quad_->vertex_description(),
+ quad_->primitive());
// Create the shader we can reuse for solid rendering.
source = std::make_unique();
@@ -391,7 +488,8 @@ bool Engine::CreateRenderResources() {
LOG << "Could not create solid shader.";
return false;
}
- solid_shader_->Create(std::move(source), quad_->vertex_description());
+ solid_shader_->Create(std::move(source), quad_->vertex_description(),
+ quad_->primitive());
return true;
}
@@ -414,10 +512,10 @@ std::unique_ptr Engine::PrintStats() {
line = "fps: ";
line += std::to_string(fps_);
lines.push_back(line);
- line = "cmd: ";
- line += std::to_string(renderer_->global_queue_size() +
- renderer_->render_queue_size());
- lines.push_back(line);
+ // line = "cmd: ";
+ // line += std::to_string(renderer_->global_queue_size() +
+ // renderer_->render_queue_size());
+ // lines.push_back(line);
constexpr int margin = 5;
int line_height = system_font_->GetLineHeight();
diff --git a/src/engine/engine.h b/src/engine/engine.h
index 89248e9..63803cd 100644
--- a/src/engine/engine.h
+++ b/src/engine/engine.h
@@ -10,6 +10,7 @@
#include "../base/random.h"
#include "../base/vecmath.h"
#include "audio/audio_forward.h"
+#include "persistent_data.h"
#include "platform/platform_forward.h"
#include "renderer/render_resource.h"
@@ -17,6 +18,7 @@ class TextureCompressor;
namespace eng {
+class Animator;
class AudioResource;
class Font;
class Game;
@@ -46,11 +48,14 @@ class Engine {
void Draw(float frame_frac);
void LostFocus();
- void GainedFocus();
+ void GainedFocus(bool from_interstitial_ad);
void AddDrawable(Drawable* drawable);
void RemoveDrawable(Drawable* drawable);
+ void AddAnimator(Animator* animator);
+ void RemoveAnimator(Animator* animator);
+
void Exit();
// Convert size from pixels to viewport scale.
@@ -83,9 +88,20 @@ class Engine {
void AddInputEvent(std::unique_ptr event);
std::unique_ptr GetNextInputEvent();
+ void StartRecording(const Json::Value& payload);
+ void EndRecording(const std::string file_name);
+
+ bool Replay(const std::string file_name, Json::Value& payload);
+
// Vibrate (if supported by the platform) for the specified duration.
void Vibrate(int duration);
+ void ShowInterstitialAd();
+
+ void ShareFile(const std::string& file_name);
+
+ void SetKeepScreenOn(bool keep_screen_on);
+
void SetImageDpi(float dpi) { image_dpi_ = dpi; }
void SetEnableAudio(bool enable);
@@ -112,13 +128,19 @@ class Engine {
// Return screen size in viewport scale.
base::Vector2 GetScreenSize() const { return screen_size_; }
- const base::Matrix4x4& GetProjectionMarix() const { return projection_; }
+ const base::Matrix4x4& GetProjectionMatrix() const { return projection_; }
int GetDeviceDpi() const;
+ float GetImageScaleFactor() const;
+
const std::string& GetRootPath() const;
- size_t GetAudioSampleRate();
+ const std::string& GetDataPath() const;
+
+ const std::string& GetSharedDataPath() const;
+
+ int GetAudioHardwareSampleRate();
bool IsMobile() const;
@@ -126,13 +148,15 @@ class Engine {
float image_dpi() const { return image_dpi_; }
+ float time_step() { return time_step_; }
+
+ int fps() const { return fps_; }
+
private:
// Class holding information about texture resources managed by engine.
- // Texture is created from an image asset if asset_file is valid. Otherwise
- // texture is created from the image returned by create_image callback.
+ // Texture is created from the image returned by create_image callback.
struct TextureResource {
std::shared_ptr texture;
- std::string asset_file;
CreateImageCB create_image;
bool persistent = false;
};
@@ -143,7 +167,7 @@ class Engine {
Renderer* renderer_ = nullptr;
- Audio* audio_;
+ Audio* audio_ = nullptr;
std::unique_ptr game_;
@@ -161,6 +185,8 @@ class Engine {
std::list drawables_;
+ std::list animators_;
+
// Textures mapped by asset name.
std::unordered_map textures_;
@@ -171,12 +197,20 @@ class Engine {
float seconds_accumulated_ = 0.0f;
+ float time_step_ = 1.0f / 60.0f;
+ size_t tick_ = 0;
+
float image_dpi_ = 200;
bool vibration_enabled_ = true;
std::deque> input_queue_;
+ PersistentData replay_data_;
+ bool recording_ = false;
+ bool replaying_ = false;
+ int replay_index_ = 0;
+
base::Random random_;
std::unique_ptr CreateRenderResourceInternal(
diff --git a/src/engine/game.h b/src/engine/game.h
index 08f123c..c6d0b19 100644
--- a/src/engine/game.h
+++ b/src/engine/game.h
@@ -16,7 +16,7 @@ class Game {
virtual void LostFocus() = 0;
- virtual void GainedFocus() = 0;
+ virtual void GainedFocus(bool from_interstitial_ad) = 0;
private:
Game(const Game&) = delete;
diff --git a/src/engine/image.cc b/src/engine/image.cc
index ee4aa7b..d50d289 100644
--- a/src/engine/image.cc
+++ b/src/engine/image.cc
@@ -151,6 +151,8 @@ bool Image::Load(const std::string& file_name) {
return false;
}
+ LOG << "Loaded " << file_name << ". number of color components: " << c;
+
uint8_t* converted_buffer = NULL;
switch (c) {
case 1:
diff --git a/src/engine/image_quad.cc b/src/engine/image_quad.cc
index a9c6b18..17c04a3 100644
--- a/src/engine/image_quad.cc
+++ b/src/engine/image_quad.cc
@@ -33,8 +33,16 @@ void ImageQuad::Destory() {
void ImageQuad::AutoScale() {
auto& engine = Engine::Get();
Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()};
- SetScale(engine.ToScale(dimensions));
- Scale((float)engine.GetDeviceDpi() / engine.image_dpi());
+ Vector2 size = engine.ToScale(dimensions);
+ float s =
+ static_cast(engine.image_dpi()) * engine.GetImageScaleFactor();
+ size *= static_cast(engine.GetDeviceDpi()) / s;
+ SetSize(size);
+}
+
+void ImageQuad::SetCustomShader(std::shared_ptr shader) {
+ custom_shader_ = shader;
+ custom_uniforms_.clear();
}
void ImageQuad::SetFrame(size_t frame) {
@@ -58,18 +66,25 @@ void ImageQuad::Draw(float frame_frac) {
Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(),
GetFrameHeight() / texture_->GetHeight()};
- Shader* shader = Engine::Get().GetPassThroughShader();
+ Shader* shader = custom_shader_ ? custom_shader_.get()
+ : Engine::Get().GetPassThroughShader();
shader->Activate();
- shader->SetUniform("offset", offset_);
- shader->SetUniform("scale", scale_);
- shader->SetUniform("pivot", pivot_);
+ shader->SetUniform("offset", position_);
+ shader->SetUniform("scale", GetSize());
shader->SetUniform("rotation", rotation_);
shader->SetUniform("tex_offset", GetUVOffset(current_frame_));
shader->SetUniform("tex_scale", tex_scale);
- shader->SetUniform("projection", Engine::Get().GetProjectionMarix());
+ shader->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader->SetUniform("color", color_);
- shader->SetUniform("texture", 0);
+ shader->SetUniform("texture_0", 0);
+ if (custom_shader_) {
+ for (auto& cu : custom_uniforms_)
+ std::visit(
+ [shader, &cu](auto&& arg) { shader->SetUniform(cu.first, arg); },
+ cu.second);
+ }
+ shader->UploadUniforms();
Engine::Get().GetQuad()->Draw();
}
diff --git a/src/engine/image_quad.h b/src/engine/image_quad.h
index 2742971..ca5f1e3 100644
--- a/src/engine/image_quad.h
+++ b/src/engine/image_quad.h
@@ -7,9 +7,12 @@
#include
#include
#include
+#include
+#include
namespace eng {
+class Shader;
class Texture;
class ImageQuad : public Animatable {
@@ -26,6 +29,13 @@ class ImageQuad : public Animatable {
void AutoScale();
+ void SetCustomShader(std::shared_ptr shader);
+
+ template
+ void SetCustomUniform(const std::string& name, T value) {
+ custom_uniforms_[name] = UniformValue(value);
+ }
+
// Animatable interface.
void SetFrame(size_t frame) override;
size_t GetFrame() const override { return current_frame_; }
@@ -37,8 +47,18 @@ class ImageQuad : public Animatable {
void Draw(float frame_frac) override;
private:
+ using UniformValue = std::variant;
+
std::shared_ptr texture_;
+ std::shared_ptr custom_shader_;
+ std::unordered_map custom_uniforms_;
+
size_t current_frame_ = 0;
std::array num_frames_ = {1, 1}; // horizontal, vertical
int frame_width_ = 0;
diff --git a/src/engine/input_event.h b/src/engine/input_event.h
index ef4b54e..8bd267c 100644
--- a/src/engine/input_event.h
+++ b/src/engine/input_event.h
@@ -31,10 +31,7 @@ class InputEvent {
size_t GetPointerId() const { return pointer_id_; }
- base::Vector2 GetVector(size_t i) const {
- DCHECK(i < 2);
- return vec_;
- }
+ base::Vector2 GetVector() const { return vec_; }
char GetKeyPress() const { return key_; }
diff --git a/src/engine/mesh.cc b/src/engine/mesh.cc
index b2301f2..570c7ed 100644
--- a/src/engine/mesh.cc
+++ b/src/engine/mesh.cc
@@ -9,10 +9,6 @@
namespace eng {
-// Used to parse the vertex layout,
-// e.g. "p3f;c4b" for "position 3 floats, color 4 bytes".
-const char Mesh::kLayoutDelimiter[] = ";/ \t";
-
bool Mesh::Create(Primitive primitive,
const std::string& vertex_description,
size_t num_vertices,
@@ -105,6 +101,8 @@ bool Mesh::Load(const std::string& file_name) {
return false;
}
+ LOG << "Loaded " << file_name << ". Vertex array size: " << vertices.size();
+
vertices_ = std::make_unique(vertex_buffer_size);
char* dst = vertices_.get();
diff --git a/src/engine/mesh.h b/src/engine/mesh.h
index e246e2d..1f866f2 100644
--- a/src/engine/mesh.h
+++ b/src/engine/mesh.h
@@ -9,8 +9,6 @@ namespace eng {
class Mesh {
public:
- static const char kLayoutDelimiter[];
-
Mesh() = default;
~Mesh() = default;
@@ -40,7 +38,7 @@ class Mesh {
bool IsValid() const { return !!vertices_.get(); }
- protected:
+ private:
Primitive primitive_ = kPrimitive_TriangleStrip;
VertexDescripton vertex_description_;
size_t num_vertices_ = 0;
diff --git a/src/engine/persistent_data.cc b/src/engine/persistent_data.cc
new file mode 100644
index 0000000..9ff3293
--- /dev/null
+++ b/src/engine/persistent_data.cc
@@ -0,0 +1,104 @@
+#include "persistent_data.h"
+
+#include
+
+#include "../base/file.h"
+#include "engine.h"
+#include "platform/asset_file.h"
+
+using namespace base;
+
+namespace eng {
+
+bool PersistentData::Load(const std::string& file_name, StorageType type) {
+ file_name_ = file_name;
+ type_ = type;
+
+ size_t size = 0;
+ std::unique_ptr buffer;
+
+ if (type == kAsset) {
+ buffer = AssetFile::ReadWholeFile(file_name, Engine::Get().GetRootPath(),
+ &size, true);
+ } else {
+ std::string file_path = type == kShared ? Engine::Get().GetSharedDataPath()
+ : Engine::Get().GetDataPath();
+ file_path += file_name;
+ ScopedFILE file;
+ file.reset(fopen(file_path.c_str(), "r"));
+ if (!file) {
+ LOG << "Failed to open file " << file_path;
+ return false;
+ }
+
+ if (file) {
+ if (!fseek(file.get(), 0, SEEK_END)) {
+ size = ftell(file.get());
+ rewind(file.get());
+ }
+ }
+
+ buffer = std::make_unique(size + 1);
+ size_t bytes_read = fread(buffer.get(), 1, size, file.get());
+ if (!bytes_read) {
+ LOG << "Failed to read a buffer of size: " << size << " from file "
+ << file_path;
+ return false;
+ }
+ buffer[size] = 0;
+ }
+
+ std::string err;
+ Json::CharReaderBuilder builder;
+ const std::unique_ptr reader(builder.newCharReader());
+ if (!reader->parse(buffer.get(), buffer.get() + size, &root_, &err)) {
+ LOG << "Failed to parse save file. Json parser error: " << err;
+ return false;
+ }
+
+ dirty_ = false;
+
+ return true;
+}
+
+bool PersistentData::Save(bool force) {
+ if (!force && !dirty_)
+ return true;
+
+ DCHECK(!file_name_.empty());
+
+ return SaveAs(file_name_, type_);
+}
+
+bool PersistentData::SaveAs(const std::string& file_name, StorageType type) {
+ DCHECK(type != kAsset);
+
+ file_name_ = file_name;
+
+ Json::StreamWriterBuilder builder;
+ std::unique_ptr writer(builder.newStreamWriter());
+ std::ostringstream stream;
+ writer->write(root_, &stream);
+
+ std::string file_path = type == kShared ? Engine::Get().GetSharedDataPath()
+ : Engine::Get().GetDataPath();
+ file_path += file_name;
+ ScopedFILE file;
+ file.reset(fopen(file_path.c_str(), "w"));
+ if (!file) {
+ LOG << "Failed to create file " << file_path;
+ return false;
+ }
+
+ std::string data = stream.str();
+ if (fwrite(data.c_str(), data.size(), 1, file.get()) != 1) {
+ LOG << "Failed to write to file " << file_path;
+ return false;
+ }
+
+ dirty_ = false;
+
+ return true;
+}
+
+} // namespace eng
diff --git a/src/engine/persistent_data.h b/src/engine/persistent_data.h
new file mode 100644
index 0000000..adfdde9
--- /dev/null
+++ b/src/engine/persistent_data.h
@@ -0,0 +1,40 @@
+#ifndef SAVE_GAME_H
+#define SAVE_GAME_H
+
+#include
+
+#include "../base/log.h"
+#include "../third_party/jsoncpp/json.h"
+
+namespace eng {
+
+class PersistentData {
+ public:
+ enum StorageType { kPrivate, kShared, kAsset };
+
+ PersistentData() = default;
+ ~PersistentData() = default;
+
+ bool Load(const std::string& file_name, StorageType type = kPrivate);
+
+ bool Save(bool force = false);
+
+ bool SaveAs(const std::string& file_name, StorageType type = kPrivate);
+
+ Json::Value& root() {
+ dirty_ = true;
+ return root_;
+ }
+
+ const Json::Value& root() const { return root_; }
+
+ private:
+ StorageType type_ = kPrivate;
+ std::string file_name_;
+ Json::Value root_;
+ bool dirty_ = false;
+};
+
+} // namespace eng
+
+#endif // SAVE_GAME_H
diff --git a/src/engine/platform/platform_android.cc b/src/engine/platform/platform_android.cc
index 068d696..50ec498 100644
--- a/src/engine/platform/platform_android.cc
+++ b/src/engine/platform/platform_android.cc
@@ -1,6 +1,7 @@
#include "platform_android.h"
#include
+#include
#include
#include
@@ -17,6 +18,22 @@ using namespace base;
namespace {
+bool g_showing_interstitial_ad = false;
+
+extern "C" {
+JNIEXPORT void JNICALL
+Java_com_kaliber_base_KaliberActivity_onShowAdResult(JNIEnv* env,
+ jobject obj,
+ jboolean succeeded);
+};
+
+JNIEXPORT void JNICALL
+Java_com_kaliber_base_KaliberActivity_onShowAdResult(JNIEnv* env,
+ jobject obj,
+ jboolean succeeded) {
+ g_showing_interstitial_ad = !!succeeded;
+}
+
std::string GetApkPath(ANativeActivity* activity) {
JNIEnv* env = nullptr;
activity->vm->AttachCurrentThread(&env, nullptr);
@@ -45,6 +62,107 @@ std::string GetApkPath(ANativeActivity* activity) {
return apk_path;
}
+std::string GetDataPath(ANativeActivity* activity) {
+ JNIEnv* env = nullptr;
+ activity->vm->AttachCurrentThread(&env, nullptr);
+
+ jclass activity_clazz = env->GetObjectClass(activity->clazz);
+ jmethodID get_dir_id = env->GetMethodID(
+ activity_clazz, "getDir", "(Ljava/lang/String;I)Ljava/io/File;");
+ jstring suffix = env->NewStringUTF("kaliber");
+ jobject data_dir_obj = env->CallObjectMethod(activity->clazz, get_dir_id,
+ suffix, 0 /* MODE_PRIVATE */);
+
+ jclass file_clazz = env->FindClass("java/io/File");
+ jmethodID get_absolute_path_id =
+ env->GetMethodID(file_clazz, "getAbsolutePath", "()Ljava/lang/String;");
+ jstring data_path_obj =
+ (jstring)env->CallObjectMethod(data_dir_obj, get_absolute_path_id);
+
+ const char* tmp = env->GetStringUTFChars(data_path_obj, nullptr);
+ std::string data_path = std::string(tmp);
+
+ env->ReleaseStringUTFChars(data_path_obj, tmp);
+ env->DeleteLocalRef(activity_clazz);
+ env->DeleteLocalRef(file_clazz);
+ env->DeleteLocalRef(suffix);
+ activity->vm->DetachCurrentThread();
+
+ if (data_path.back() != '/')
+ data_path += '/';
+ return data_path;
+}
+
+std::string GetSharedDataPath(ANativeActivity* activity) {
+ JNIEnv* env = nullptr;
+ activity->vm->AttachCurrentThread(&env, nullptr);
+
+ jclass activity_clazz = env->GetObjectClass(activity->clazz);
+ jmethodID get_dir_id = env->GetMethodID(activity_clazz, "getExternalFilesDir",
+ "(Ljava/lang/String;)Ljava/io/File;");
+ jobject data_dir_obj =
+ env->CallObjectMethod(activity->clazz, get_dir_id, nullptr);
+
+ jclass file_clazz = env->FindClass("java/io/File");
+ jmethodID get_absolute_path_id =
+ env->GetMethodID(file_clazz, "getAbsolutePath", "()Ljava/lang/String;");
+ jstring data_path_obj =
+ (jstring)env->CallObjectMethod(data_dir_obj, get_absolute_path_id);
+
+ const char* tmp = env->GetStringUTFChars(data_path_obj, nullptr);
+ std::string data_path = std::string(tmp);
+
+ env->ReleaseStringUTFChars(data_path_obj, tmp);
+ env->DeleteLocalRef(activity_clazz);
+ env->DeleteLocalRef(file_clazz);
+ activity->vm->DetachCurrentThread();
+
+ if (data_path.back() != '/')
+ data_path += '/';
+ return data_path;
+}
+
+void ShowInterstitialAd(ANativeActivity* activity) {
+ JNIEnv* env = nullptr;
+ activity->vm->AttachCurrentThread(&env, nullptr);
+
+ jclass activity_clazz = env->GetObjectClass(activity->clazz);
+ jmethodID show_interstitial_ad =
+ env->GetMethodID(activity_clazz, "showInterstitialAd", "()V");
+ env->CallVoidMethod(activity->clazz, show_interstitial_ad);
+
+ env->DeleteLocalRef(activity_clazz);
+ activity->vm->DetachCurrentThread();
+}
+
+void ShareFile(ANativeActivity* activity, const std::string& file_name) {
+ JNIEnv* env = nullptr;
+ activity->vm->AttachCurrentThread(&env, nullptr);
+
+ jclass activity_clazz = env->GetObjectClass(activity->clazz);
+ jmethodID show_interstitial_ad =
+ env->GetMethodID(activity_clazz, "shareFile", "(Ljava/lang/String;)V");
+ jstring file_name_js = env->NewStringUTF(file_name.c_str());
+ env->CallVoidMethod(activity->clazz, show_interstitial_ad, file_name_js);
+
+ env->DeleteLocalRef(activity_clazz);
+ env->DeleteLocalRef(file_name_js);
+ activity->vm->DetachCurrentThread();
+}
+
+void SetKeepScreenOn(ANativeActivity* activity, bool keep_screen_on) {
+ JNIEnv* env = nullptr;
+ activity->vm->AttachCurrentThread(&env, nullptr);
+
+ jclass activity_clazz = env->GetObjectClass(activity->clazz);
+ jmethodID method_id =
+ env->GetMethodID(activity_clazz, "setKeepScreenOn", "(Z)V");
+ env->CallVoidMethod(activity->clazz, method_id, (jboolean)keep_screen_on);
+
+ env->DeleteLocalRef(activity_clazz);
+ activity->vm->DetachCurrentThread();
+}
+
void Vibrate(ANativeActivity* activity, int duration) {
JNIEnv* env = nullptr;
activity->vm->AttachCurrentThread(&env, nullptr);
@@ -78,7 +196,7 @@ void Vibrate(ANativeActivity* activity, int duration) {
activity->vm->DetachCurrentThread();
}
-int32_t getDensityDpi(android_app* app) {
+int32_t GetDensityDpi(android_app* app) {
AConfiguration* config = AConfiguration_new();
AConfiguration_fromAssetManager(config, app->activity->assetManager);
int32_t density = AConfiguration_getDensity(config);
@@ -129,7 +247,7 @@ int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
switch (flags) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
- DLOG << "AMOTION_EVENT_ACTION_DOWN - pointer_id: " << pointer_id;
+ // DLOG << "AMOTION_EVENT_ACTION_DOWN - pointer_id: " << pointer_id;
platform->pointer_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = true;
input_event =
@@ -139,7 +257,7 @@ int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
- DLOG << "AMOTION_EVENT_ACTION_UP - pointer_id: " << pointer_id;
+ // DLOG << "AMOTION_EVENT_ACTION_UP - pointer_id: " << pointer_id;
platform->pointer_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = false;
input_event = std::make_unique(
@@ -220,7 +338,8 @@ void PlatformAndroid::HandleCmd(android_app* app, int32_t cmd) {
platform->timer_.Reset();
platform->has_focus_ = true;
if (platform->engine_)
- platform->engine_->GainedFocus();
+ platform->engine_->GainedFocus(g_showing_interstitial_ad);
+ g_showing_interstitial_ad = false;
break;
case APP_CMD_LOST_FOCUS:
@@ -243,10 +362,16 @@ void PlatformAndroid::Initialize(android_app* app) {
mobile_device_ = true;
- root_path_ = GetApkPath(app->activity);
+ root_path_ = ::GetApkPath(app->activity);
LOG << "Root path: " << root_path_.c_str();
- device_dpi_ = getDensityDpi(app);
+ data_path_ = ::GetDataPath(app->activity);
+ LOG << "Data path: " << data_path_.c_str();
+
+ shared_data_path_ = ::GetSharedDataPath(app->activity);
+ LOG << "Shared data path: " << shared_data_path_.c_str();
+
+ device_dpi_ = ::GetDensityDpi(app);
LOG << "Device DPI: " << device_dpi_;
app->userData = reinterpret_cast(this);
@@ -283,6 +408,18 @@ void PlatformAndroid::Vibrate(int duration) {
::Vibrate(app_->activity, duration);
}
+void PlatformAndroid::ShowInterstitialAd() {
+ ::ShowInterstitialAd(app_->activity);
+}
+
+void PlatformAndroid::ShareFile(const std::string& file_name) {
+ ::ShareFile(app_->activity, file_name);
+}
+
+void PlatformAndroid::SetKeepScreenOn(bool keep_screen_on) {
+ ::SetKeepScreenOn(app_->activity, keep_screen_on);
+}
+
} // namespace eng
void android_main(android_app* app) {
diff --git a/src/engine/platform/platform_android.h b/src/engine/platform/platform_android.h
index 430b1de..2cee0c7 100644
--- a/src/engine/platform/platform_android.h
+++ b/src/engine/platform/platform_android.h
@@ -22,6 +22,12 @@ class PlatformAndroid : public PlatformBase {
void Vibrate(int duration);
+ void ShowInterstitialAd();
+
+ void ShareFile(const std::string& file_name);
+
+ void SetKeepScreenOn(bool keep_screen_on);
+
private:
android_app* app_ = nullptr;
diff --git a/src/engine/platform/platform_base.cc b/src/engine/platform/platform_base.cc
index e519e31..e46c9b2 100644
--- a/src/engine/platform/platform_base.cc
+++ b/src/engine/platform/platform_base.cc
@@ -1,15 +1,10 @@
#include "platform.h"
-#include
-
#include "../../base/log.h"
#include "../../base/task_runner.h"
#include "../audio/audio.h"
#include "../engine.h"
-#include "../renderer/renderer.h"
-
-// Save battery on mobile devices.
-#define USE_SLEEP
+#include "../renderer/opengl/renderer_opengl.h"
using namespace base;
@@ -33,7 +28,7 @@ void PlatformBase::Initialize() {
throw internal_error;
}
- renderer_ = std::make_unique();
+ renderer_ = std::make_unique();
}
void PlatformBase::Shutdown() {
@@ -52,11 +47,7 @@ void PlatformBase::RunMainLoop() {
}
// Use fixed time steps.
- constexpr float time_step = 1.0f / 60.0f;
-
-#ifdef USE_SLEEP
- constexpr float epsilon = 0.0001f;
-#endif // USE_SLEEP
+ float time_step = engine_->time_step();
timer_.Reset();
float accumulator = 0.0;
@@ -66,20 +57,8 @@ void PlatformBase::RunMainLoop() {
engine_->Draw(frame_frac);
// Accumulate time.
-#ifdef USE_SLEEP
- while (accumulator < time_step) {
- timer_.Update();
- accumulator += timer_.GetSecondsPassed();
- if (time_step - accumulator > epsilon) {
- float sleep_time = time_step - accumulator - epsilon;
- std::this_thread::sleep_for(
- std::chrono::microseconds((int)(sleep_time * 1000000.0f)));
- }
- };
-#else
timer_.Update();
accumulator += timer_.GetSecondsPassed();
-#endif // USE_SLEEP
// Subdivide the frame time.
while (accumulator >= time_step) {
diff --git a/src/engine/platform/platform_base.h b/src/engine/platform/platform_base.h
index 864fd2c..a40dfdf 100644
--- a/src/engine/platform/platform_base.h
+++ b/src/engine/platform/platform_base.h
@@ -26,6 +26,10 @@ class PlatformBase {
const std::string& GetRootPath() const { return root_path_; }
+ const std::string& GetDataPath() const { return data_path_; }
+
+ const std::string& GetSharedDataPath() const { return shared_data_path_; }
+
bool mobile_device() const { return mobile_device_; }
static class InternalError : public std::exception {
@@ -35,8 +39,10 @@ class PlatformBase {
base::Timer timer_;
bool mobile_device_ = false;
- int device_dpi_ = 200;
+ int device_dpi_ = 100;
std::string root_path_;
+ std::string data_path_;
+ std::string shared_data_path_;
bool has_focus_ = false;
bool should_exit_ = false;
diff --git a/src/engine/platform/platform_linux.cc b/src/engine/platform/platform_linux.cc
index 314b867..64e88bf 100644
--- a/src/engine/platform/platform_linux.cc
+++ b/src/engine/platform/platform_linux.cc
@@ -1,8 +1,5 @@
#include "platform_linux.h"
-#include
-#include
-
#include
#include "../../base/log.h"
@@ -26,25 +23,39 @@ void PlatformLinux::Initialize() {
root_path_ = "../../";
LOG << "Root path: " << root_path_.c_str();
- if (!renderer_->Initialize()) {
+ data_path_ = "./";
+ LOG << "Data path: " << data_path_.c_str();
+
+ shared_data_path_ = "./";
+ LOG << "Shared data path: " << shared_data_path_.c_str();
+
+ if (!CreateWindow(800, 1205)) {
+ LOG << "Failed to create window.";
+ throw internal_error;
+ }
+
+ if (!renderer_->Initialize(display_, window_)) {
LOG << "Failed to initialize renderer.";
throw internal_error;
}
- Display* display = renderer_->display();
- Window window = renderer_->window();
- XSelectInput(display, window,
+ XSelectInput(display_, window_,
KeyPressMask | Button1MotionMask | ButtonPressMask |
ButtonReleaseMask | FocusChangeMask);
- Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false);
- XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);
+ Atom WM_DELETE_WINDOW = XInternAtom(display_, "WM_DELETE_WINDOW", false);
+ XSetWMProtocols(display_, window_, &WM_DELETE_WINDOW, 1);
+}
+
+void PlatformLinux::Shutdown() {
+ PlatformBase::Shutdown();
+
+ DestroyWindow();
}
void PlatformLinux::Update() {
- Display* display = renderer_->display();
- while (XPending(display)) {
+ while (XPending(display_)) {
XEvent e;
- XNextEvent(display, &e);
+ XNextEvent(display_, &e);
switch (e.type) {
case KeyPress: {
KeySym key = XLookupKeysym(&e.xkey, 0);
@@ -90,7 +101,7 @@ void PlatformLinux::Update() {
break;
}
case FocusIn: {
- engine_->GainedFocus();
+ engine_->GainedFocus(false);
break;
}
case ClientMessage: {
@@ -106,6 +117,47 @@ void PlatformLinux::Exit() {
should_exit_ = true;
}
+bool PlatformLinux::CreateWindow(int width, int height) {
+ // Try to open the local display.
+ display_ = XOpenDisplay(NULL);
+ if (!display_) {
+ LOG << "Can't connect to X server. Try to set the DISPLAY environment "
+ "variable (hostname:number.screen_number).";
+ return false;
+ }
+
+ Window root_window = DefaultRootWindow(display_);
+
+ XVisualInfo* visual_info = renderer_->GetXVisualInfo(display_);
+ if (!visual_info) {
+ LOG << "No appropriate visual found.";
+ return false;
+ }
+ LOG << "Visual " << (void*)visual_info->visualid << " selected";
+
+ // Create the main window.
+ XSetWindowAttributes window_attributes;
+ window_attributes.colormap =
+ XCreateColormap(display_, root_window, visual_info->visual, AllocNone);
+ window_attributes.event_mask = ExposureMask | KeyPressMask;
+ window_ = XCreateWindow(display_, root_window, 0, 0, width, height, 0,
+ visual_info->depth, InputOutput, visual_info->visual,
+ CWColormap | CWEventMask, &window_attributes);
+ XMapWindow(display_, window_);
+ XStoreName(display_, window_, "kaliber");
+
+ return true;
+}
+
+void PlatformLinux::DestroyWindow() {
+ if (display_) {
+ XDestroyWindow(display_, window_);
+ XCloseDisplay(display_);
+ display_ = nullptr;
+ window_ = 0;
+ }
+}
+
} // namespace eng
int main(int argc, char** argv) {
diff --git a/src/engine/platform/platform_linux.h b/src/engine/platform/platform_linux.h
index a89b21d..efb7385 100644
--- a/src/engine/platform/platform_linux.h
+++ b/src/engine/platform/platform_linux.h
@@ -1,6 +1,9 @@
#ifndef PLATFORM_LINUX_H
#define PLATFORM_LINUX_H
+#include
+#include
+
#include "platform_base.h"
namespace eng {
@@ -12,11 +15,26 @@ class PlatformLinux : public PlatformBase {
void Initialize();
+ void Shutdown();
+
void Update();
void Exit();
void Vibrate(int duration) {}
+
+ void ShowInterstitialAd() {}
+
+ void ShareFile(const std::string& file_name) {}
+
+ void SetKeepScreenOn(bool keep_screen_on) {}
+
+ private:
+ Display* display_ = nullptr;
+ Window window_ = 0;
+
+ bool CreateWindow(int width, int height);
+ void DestroyWindow();
};
} // namespace eng
diff --git a/src/engine/renderer/geometry.cc b/src/engine/renderer/geometry.cc
index 3c2f866..9ff312f 100644
--- a/src/engine/renderer/geometry.cc
+++ b/src/engine/renderer/geometry.cc
@@ -1,8 +1,6 @@
#include "geometry.h"
-#include "../engine.h"
#include "../mesh.h"
-#include "render_command.h"
#include "renderer.h"
namespace eng {
@@ -19,30 +17,21 @@ Geometry::~Geometry() {
void Geometry::Create(std::unique_ptr mesh) {
Destroy();
valid_ = true;
-
vertex_description_ = mesh->vertex_description();
-
- auto cmd = std::make_unique();
- cmd->mesh = std::move(mesh);
- cmd->impl_data = impl_data_;
- renderer_->EnqueueCommand(std::move(cmd));
+ primitive_ = mesh->primitive();
+ renderer_->CreateGeometry(impl_data_, std::move(mesh));
}
void Geometry::Destroy() {
if (valid_) {
- auto cmd = std::make_unique();
- cmd->impl_data = impl_data_;
- renderer_->EnqueueCommand(std::move(cmd));
+ renderer_->DestroyGeometry(impl_data_);
valid_ = false;
}
}
void Geometry::Draw() {
- if (valid_) {
- auto cmd = std::make_unique();
- cmd->impl_data = impl_data_;
- renderer_->EnqueueCommand(std::move(cmd));
- }
+ if (valid_)
+ renderer_->Draw(impl_data_);
}
} // namespace eng
diff --git a/src/engine/renderer/geometry.h b/src/engine/renderer/geometry.h
index 888c559..78c9f79 100644
--- a/src/engine/renderer/geometry.h
+++ b/src/engine/renderer/geometry.h
@@ -29,8 +29,11 @@ class Geometry : public RenderResource {
return vertex_description_;
}
+ Primitive primitive() { return primitive_; }
+
private:
VertexDescripton vertex_description_;
+ Primitive primitive_ = kPrimitive_Invalid;
};
} // namespace eng
diff --git a/src/engine/renderer/opengl.h b/src/engine/renderer/opengl/opengl.h
similarity index 74%
rename from src/engine/renderer/opengl.h
rename to src/engine/renderer/opengl/opengl.h
index f279765..5549cbf 100644
--- a/src/engine/renderer/opengl.h
+++ b/src/engine/renderer/opengl/opengl.h
@@ -5,12 +5,12 @@
// Use the modified Khronos header from ndk-helper. This gives access to
// additional functionality the drivers may expose but which the system headers
// do not.
-#include "../../third_party/android/gl3stub.h"
+#include "../../../third_party/android/gl3stub.h"
#include
#elif defined(__linux__)
-#include "../../third_party/glew/glew.h"
-#include "../../third_party/glew/glxew.h"
+#include "../../../third_party/glew/glew.h"
+#include "../../../third_party/glew/glxew.h"
// Define the missing format for the etc1
#ifndef GL_ETC1_RGB8_OES
diff --git a/src/engine/renderer/render_command.cc b/src/engine/renderer/opengl/render_command.cc
similarity index 92%
rename from src/engine/renderer/render_command.cc
rename to src/engine/renderer/opengl/render_command.cc
index a4ee8ab..deed111 100644
--- a/src/engine/renderer/render_command.cc
+++ b/src/engine/renderer/opengl/render_command.cc
@@ -1,8 +1,8 @@
#include "render_command.h"
-#include "../image.h"
-#include "../mesh.h"
-#include "../shader_source.h"
+#include "../../image.h"
+#include "../../mesh.h"
+#include "../../shader_source.h"
#ifdef _DEBUG
#define RENDER_COMMAND_IMPL(NAME, GLOBAL) \
diff --git a/src/engine/renderer/render_command.h b/src/engine/renderer/opengl/render_command.h
similarity index 94%
rename from src/engine/renderer/render_command.h
rename to src/engine/renderer/opengl/render_command.h
index db1b91e..51289be 100644
--- a/src/engine/renderer/render_command.h
+++ b/src/engine/renderer/opengl/render_command.h
@@ -5,9 +5,9 @@
#include
#include
-#include "../../base/hash.h"
-#include "../../base/vecmath.h"
-#include "renderer_types.h"
+#include "../../../base/hash.h"
+#include "../../../base/vecmath.h"
+#include "../renderer_types.h"
namespace eng {
@@ -39,7 +39,7 @@ struct RenderCommand {
const CommandId cmd_id = INVALID_CMD_ID;
// Global render commands are guaranteed to be processed. Others commands are
- // frame specific and can be discared by the renderer.
+ // frame specific and can be discared by the renderer if not throttled.
const bool global = false;
#ifdef _DEBUG
diff --git a/src/engine/renderer/renderer.cc b/src/engine/renderer/opengl/renderer_opengl.cc
similarity index 74%
rename from src/engine/renderer/renderer.cc
rename to src/engine/renderer/opengl/renderer_opengl.cc
index 7249a82..b6bafb4 100644
--- a/src/engine/renderer/renderer.cc
+++ b/src/engine/renderer/opengl/renderer_opengl.cc
@@ -1,21 +1,22 @@
-#include "renderer.h"
+#include "renderer_opengl.h"
#include
#include
#include
+#include
-#include "../../base/log.h"
-#include "../../base/vecmath.h"
+#include "../../../base/log.h"
+#include "../../../base/vecmath.h"
#ifdef THREADED_RENDERING
-#include "../../base/task_runner.h"
+#include "../../../base/task_runner.h"
#endif // THREADED_RENDERING
-#include "../image.h"
-#include "../mesh.h"
-#include "../shader_source.h"
-#include "geometry.h"
+#include "../../image.h"
+#include "../../mesh.h"
+#include "../../shader_source.h"
+#include "../geometry.h"
+#include "../shader.h"
+#include "../texture.h"
#include "render_command.h"
-#include "shader.h"
-#include "texture.h"
using namespace base;
@@ -29,44 +30,170 @@ constexpr GLenum kGlDataType[eng::kDataType_Max] = {
GL_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_SHORT};
const std::string kAttributeNames[eng::kAttribType_Max] = {
- "inColor", "inNormal", "inPosition", "inTexCoord"};
+ "in_color", "in_normal", "in_position", "in_tex_coord"};
} // namespace
namespace eng {
#ifdef THREADED_RENDERING
-Renderer::Renderer()
+RendererOpenGL::RendererOpenGL()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
#else
-Renderer::Renderer() = default;
+RendererOpenGL::RendererOpenGL() = default;
#endif // THREADED_RENDERING
-Renderer::~Renderer() = default;
+RendererOpenGL::~RendererOpenGL() = default;
-void Renderer::SetContextLostCB(Closure cb) {
- context_lost_cb_ = std::move(cb);
+void RendererOpenGL::CreateGeometry(std::shared_ptr impl_data,
+ std::unique_ptr mesh) {
+ auto cmd = std::make_unique();
+ cmd->mesh = std::move(mesh);
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
}
-void Renderer::ContextLost() {
+void RendererOpenGL::DestroyGeometry(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::Draw(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::UpdateTexture(std::shared_ptr impl_data,
+ std::unique_ptr image) {
+ auto cmd = std::make_unique();
+ cmd->image = std::move(image);
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::DestroyTexture(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::ActivateTexture(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::CreateShader(std::shared_ptr impl_data,
+ std::unique_ptr source,
+ const VertexDescripton& vertex_description,
+ Primitive primitive) {
+ auto cmd = std::make_unique();
+ cmd->source = std::move(source);
+ cmd->vertex_description = vertex_description;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::DestroyShader(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::ActivateShader(std::shared_ptr impl_data) {
+ auto cmd = std::make_unique();
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ const base::Vector2& val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->v = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ const base::Vector3& val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->v = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ const base::Vector4& val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->v = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ const base::Matrix4x4& val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->m = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ float val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->f = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::SetUniform(std::shared_ptr impl_data,
+ const std::string& name,
+ int val) {
+ auto cmd = std::make_unique();
+ cmd->name = name;
+ cmd->i = val;
+ cmd->impl_data = impl_data;
+ EnqueueCommand(std::move(cmd));
+}
+
+void RendererOpenGL::Present() {
+ EnqueueCommand(std::make_unique());
+#ifdef THREADED_RENDERING
+ draw_complete_semaphore_.Acquire();
+#endif // THREADED_RENDERING
+ fps_++;
+}
+
+void RendererOpenGL::ContextLost() {
LOG << "Context lost.";
#ifdef THREADED_RENDERING
global_commands_.clear();
draw_commands_[0].clear();
draw_commands_[1].clear();
-#endif // THREADED_RENDERING
- InvalidateAllResources();
-
-#ifdef THREADED_RENDERING
- main_thread_task_runner_->EnqueueTask(HERE, context_lost_cb_);
+ main_thread_task_runner_->PostTask(
+ HERE, std::bind(&RendererOpenGL::InvalidateAllResources, this));
+ main_thread_task_runner_->PostTask(HERE, context_lost_cb_);
#else
+ InvalidateAllResources();
context_lost_cb_();
#endif // THREADED_RENDERING
}
-std::unique_ptr Renderer::CreateResource(
+std::unique_ptr RendererOpenGL::CreateResource(
RenderResourceFactoryBase& factory) {
static unsigned last_id = 0;
@@ -88,48 +215,19 @@ std::unique_ptr Renderer::CreateResource(
return resource;
}
-void Renderer::ReleaseResource(unsigned resource_id) {
+void RendererOpenGL::ReleaseResource(unsigned resource_id) {
auto it = resources_.find(resource_id);
if (it != resources_.end())
resources_.erase(it);
}
-void Renderer::EnqueueCommand(std::unique_ptr cmd) {
-#ifdef THREADED_RENDERING
- if (cmd->global) {
- {
- std::unique_lock scoped_lock(mutex_);
- global_commands_.push_back(std::move(cmd));
- }
- cv_.notify_one();
- global_queue_size_ = global_commands_.size();
- return;
- }
-
- bool new_frame = cmd->cmd_id == CmdPresent::CMD_ID;
- draw_commands_[1].push_back(std::move(cmd));
- if (new_frame) {
- render_queue_size_ = draw_commands_[1].size();
- {
- std::unique_lock scoped_lock(mutex_);
- draw_commands_[0].swap(draw_commands_[1]);
- }
- cv_.notify_one();
- fps_ += draw_commands_[1].size() ? 0 : 1;
- draw_commands_[1].clear();
- }
-#else
- ProcessCommand(cmd.get());
-#endif // THREADED_RENDERING
-}
-
-size_t Renderer::GetAndResetFPS() {
+size_t RendererOpenGL::GetAndResetFPS() {
int ret = fps_;
fps_ = 0;
return ret;
}
-bool Renderer::InitCommon() {
+bool RendererOpenGL::InitCommon() {
// Get information about the currently active context.
const char* renderer =
reinterpret_cast(glGetString(GL_RENDERER));
@@ -187,10 +285,8 @@ bool Renderer::InitCommon() {
}
// Ancient hardware is not supported.
- if (!npot_) {
+ if (!npot_)
LOG << "NPOT not supported.";
- return false;
- }
if (vertex_array_objects_)
LOG << "Supports Vertex Array Objects.";
@@ -211,12 +307,12 @@ bool Renderer::InitCommon() {
return true;
}
-void Renderer::InvalidateAllResources() {
+void RendererOpenGL::InvalidateAllResources() {
for (auto& r : resources_)
r.second->Destroy();
}
-bool Renderer::StartRenderThread() {
+bool RendererOpenGL::StartRenderThread() {
#ifdef THREADED_RENDERING
LOG << "Starting render thread.";
@@ -228,7 +324,7 @@ bool Renderer::StartRenderThread() {
std::promise promise;
std::future future = promise.get_future();
render_thread_ =
- std::thread(&Renderer::RenderThreadMain, this, std::move(promise));
+ std::thread(&RendererOpenGL::RenderThreadMain, this, std::move(promise));
future.wait();
return future.get();
#else
@@ -237,7 +333,7 @@ bool Renderer::StartRenderThread() {
#endif // THREADED_RENDERING
}
-void Renderer::TerminateRenderThread() {
+void RendererOpenGL::TerminateRenderThread() {
#ifdef THREADED_RENDERING
DCHECK(!terminate_render_thread_);
@@ -256,7 +352,7 @@ void Renderer::TerminateRenderThread() {
#ifdef THREADED_RENDERING
-void Renderer::RenderThreadMain(std::promise promise) {
+void RendererOpenGL::RenderThreadMain(std::promise promise) {
promise.set_value(InitInternal());
std::deque> cq[2];
@@ -291,7 +387,33 @@ void Renderer::RenderThreadMain(std::promise promise) {
#endif // THREADED_RENDERING
-void Renderer::ProcessCommand(RenderCommand* cmd) {
+void RendererOpenGL::EnqueueCommand(std::unique_ptr cmd) {
+#ifdef THREADED_RENDERING
+ if (cmd->global) {
+ {
+ std::unique_lock scoped_lock(mutex_);
+ global_commands_.push_back(std::move(cmd));
+ }
+ cv_.notify_one();
+ return;
+ }
+
+ bool new_frame = cmd->cmd_id == CmdPresent::CMD_ID;
+ draw_commands_[1].push_back(std::move(cmd));
+ if (new_frame) {
+ {
+ std::unique_lock scoped_lock(mutex_);
+ draw_commands_[0].swap(draw_commands_[1]);
+ }
+ cv_.notify_one();
+ draw_commands_[1].clear();
+ }
+#else
+ ProcessCommand(cmd.get());
+#endif // THREADED_RENDERING
+}
+
+void RendererOpenGL::ProcessCommand(RenderCommand* cmd) {
#if 0
LOG << "Processing command: " << cmd->cmd_name.c_str();
#endif
@@ -350,7 +472,7 @@ void Renderer::ProcessCommand(RenderCommand* cmd) {
}
}
-void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
+void RendererOpenGL::HandleCmdUpdateTexture(RenderCommand* cmd) {
auto* c = static_cast(cmd);
auto impl_data = reinterpret_cast(c->impl_data.get());
bool new_texture = impl_data->id == 0;
@@ -418,7 +540,7 @@ void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) {
}
}
-void Renderer::HandleCmdDestoryTexture(RenderCommand* cmd) {
+void RendererOpenGL::HandleCmdDestoryTexture(RenderCommand* cmd) {
auto* c = static_cast(cmd);
auto impl_data = reinterpret_cast(c->impl_data.get());
if (impl_data->id > 0) {
@@ -427,14 +549,16 @@ void Renderer::HandleCmdDestoryTexture(RenderCommand* cmd) {
}
}
-void Renderer::HandleCmdActivateTexture(RenderCommand* cmd) {
+void RendererOpenGL::HandleCmdActivateTexture(RenderCommand* cmd) {
auto* c = static_cast(cmd);
auto impl_data = reinterpret_cast(c->impl_data.get());
- if (impl_data->id > 0)
+ if (impl_data->id > 0 && impl_data->id != active_texture_id_) {
glBindTexture(GL_TEXTURE_2D, impl_data->id);
+ active_texture_id_ = impl_data->id;
+ }
}
-void Renderer::HandleCmdCreateGeometry(RenderCommand* cmd) {
+void RendererOpenGL::HandleCmdCreateGeometry(RenderCommand* cmd) {
auto* c = static_cast(cmd);
auto impl_data = reinterpret_cast