- Implement PersistentData.
- Update jsoncpp (1.9.3).
- Update logging implementation.
- New audio resampler (SincResampler).
- Avoid busy-looping in audio thread (AudioAlsa).
- Add BindWeak.
- AdMob support.
- Support for record/replay user input.
- Custom shader support for ImageQuad.
- Implement SetKeepScreenOn for Android.
- Move animation updates to Engine.
- Code refactoring for renderer.
- Various fixes and code cleanup.
This commit is contained in:
Attila Uygun 2020-09-18 00:03:21 +02:00
parent 9d92d01be1
commit 7920d9f251
120 changed files with 5349 additions and 12856 deletions

View File

@ -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

View File

@ -1,5 +1,6 @@
A simple, cross-platform 2D game engine with OpenGL renderer. Supports Linux and 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 #### Building the demo
Linux: Linux:
```text ```text
@ -21,7 +22,6 @@ ninja -C out/release
[jsoncpp](https://github.com/open-source-parsers/jsoncpp), [jsoncpp](https://github.com/open-source-parsers/jsoncpp),
[minimp3](https://github.com/lieff/minimp3), [minimp3](https://github.com/lieff/minimp3),
[oboe](https://github.com/google/oboe), [oboe](https://github.com/google/oboe),
[r8brain-free-src](https://github.com/avaneev/r8brain-free-src),
[stb](https://github.com/nothings/stb), [stb](https://github.com/nothings/stb),
[texture-compressor](https://github.com/auygun/kaliber/tree/master/src/third_party/texture_compressor), [texture-compressor](https://github.com/auygun/kaliber/tree/master/src/third_party/texture_compressor),
[minizip](https://github.com/madler/zlib/tree/master/contrib/minizip) [minizip](https://github.com/madler/zlib/tree/master/contrib/minizip)

View File

@ -3,10 +3,10 @@ precision mediump float;
#endif #endif
uniform vec4 color; uniform vec4 color;
uniform sampler2D texture; uniform sampler2D texture_0;
varying vec2 tex_coord_0; varying vec2 tex_coord_0;
void main() { void main() {
gl_FragColor = texture2D(texture, tex_coord_0) * color; gl_FragColor = texture2D(texture_0, tex_coord_0) * color;
} }

View File

@ -3,7 +3,6 @@ attribute vec2 in_tex_coord_0;
uniform vec2 scale; uniform vec2 scale;
uniform vec2 offset; uniform vec2 offset;
uniform vec2 pivot;
uniform vec2 rotation; uniform vec2 rotation;
uniform vec2 tex_offset; uniform vec2 tex_offset;
uniform vec2 tex_scale; uniform vec2 tex_scale;
@ -15,10 +14,9 @@ void main() {
// Simple 2d transform. // Simple 2d transform.
vec2 position = in_position; vec2 position = in_position;
position *= scale; position *= scale;
position += pivot;
position = vec2(position.x * rotation.y + position.y * rotation.x, position = vec2(position.x * rotation.y + position.y * rotation.x,
position.y * rotation.y - position.x * 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; tex_coord_0 = (in_tex_coord_0 + tex_offset) * tex_scale;

View File

@ -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]
}

View File

@ -3,7 +3,6 @@ attribute vec2 in_tex_coord_0;
uniform vec2 scale; uniform vec2 scale;
uniform vec2 offset; uniform vec2 offset;
uniform vec2 pivot;
uniform vec2 rotation; uniform vec2 rotation;
uniform mat4 projection; uniform mat4 projection;
@ -11,10 +10,9 @@ void main() {
// Simple 2d transform. // Simple 2d transform.
vec2 position = in_position; vec2 position = in_position;
position *= scale; position *= scale;
position += pivot;
position = vec2(position.x * rotation.y + position.y * rotation.x, position = vec2(position.x * rotation.y + position.y * rotation.x,
position.y * rotation.y - position.x * rotation.x); position.y * rotation.y - position.x * rotation.x);
position += offset - pivot; position += offset;
gl_Position = projection * vec4(position, 0.0, 1.0); gl_Position = projection * vec4(position, 0.0, 1.0);
} }

View File

@ -1 +0,0 @@
[![Build Status](https://travis-ci.org/auygun/kaliber.svg?branch=master)](https://travis-ci.org/auygun/kaliber)

View File

@ -47,10 +47,11 @@ endif ()
set(CMAKE_SHARED_LINKER_FLAGS set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
add_library(native-activity SHARED add_library(kaliber SHARED
../../../src/base/collusion_test.cc ../../../src/base/collusion_test.cc
../../../src/base/log.cc ../../../src/base/log.cc
../../../src/base/random.cc ../../../src/base/random.cc
../../../src/base/sinc_resampler.cc
../../../src/base/task_runner.cc ../../../src/base/task_runner.cc
../../../src/base/timer.cc ../../../src/base/timer.cc
../../../src/base/vecmath.cc ../../../src/base/vecmath.cc
@ -73,16 +74,17 @@ add_library(native-activity SHARED
../../../src/engine/image_quad.cc ../../../src/engine/image_quad.cc
../../../src/engine/image.cc ../../../src/engine/image.cc
../../../src/engine/mesh.cc ../../../src/engine/mesh.cc
../../../src/engine/persistent_data.cc
../../../src/engine/platform/asset_file_android.cc ../../../src/engine/platform/asset_file_android.cc
../../../src/engine/platform/asset_file.cc ../../../src/engine/platform/asset_file.cc
../../../src/engine/platform/platform_android.cc ../../../src/engine/platform/platform_android.cc
../../../src/engine/platform/platform_base.cc ../../../src/engine/platform/platform_base.cc
../../../src/engine/renderer/geometry.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/render_resource.cc
../../../src/engine/renderer/renderer_android.cc
../../../src/engine/renderer/renderer_types.cc ../../../src/engine/renderer/renderer_types.cc
../../../src/engine/renderer/renderer.cc
../../../src/engine/renderer/shader.cc ../../../src/engine/renderer/shader.cc
../../../src/engine/renderer/texture.cc ../../../src/engine/renderer/texture.cc
../../../src/engine/shader_source.cc ../../../src/engine/shader_source.cc
@ -94,8 +96,6 @@ add_library(native-activity SHARED
../../../src/third_party/jsoncpp/jsoncpp.cc ../../../src/third_party/jsoncpp/jsoncpp.cc
../../../src/third_party/minizip/ioapi.c ../../../src/third_party/minizip/ioapi.c
../../../src/third_party/minizip/unzip.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_internals.cc
../../../src/third_party/texture_compressor/dxt_encoder.cc ../../../src/third_party/texture_compressor/dxt_encoder.cc
../../../src/third_party/texture_compressor/texture_compressor_etc1.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) if (ANDROID_ABI STREQUAL armeabi-v7a)
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc) target_sources(kaliber 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/texture_compressor_etc1_neon.cc)
set_source_files_properties(../../../src/third_party/r8b/pffft.cpp PROPERTIES COMPILE_FLAGS -mfpu=neon)
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/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) set_source_files_properties(../../../src/third_party/texture_compressor/texture_compressor_etc1_neon.cc PROPERTIES COMPILE_FLAGS -mfpu=neon)
endif() endif()
if (ANDROID_ABI STREQUAL arm64-v8a) if (ANDROID_ABI STREQUAL arm64-v8a)
target_sources(native-activity PRIVATE ../../../src/third_party/texture_compressor/dxt_encoder_neon.cc) target_sources(kaliber 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/texture_compressor_etc1_neon.cc)
endif() endif()
target_include_directories(native-activity PRIVATE target_include_directories(kaliber PRIVATE
${ANDROID_NDK}/sources/android/native_app_glue ${ANDROID_NDK}/sources/android/native_app_glue
) )
# add lib dependencies # add lib dependencies
target_link_libraries(native-activity target_link_libraries(kaliber
android android
native_app_glue native_app_glue
oboe oboe

View File

@ -2,22 +2,29 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 29 compileSdkVersion 29
ndkVersion '21.3.6528147'
defaultConfig { defaultConfig {
applicationId = 'com.example.native_activity' applicationId = 'com.kaliber.demo'
minSdkVersion 14 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 29
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments '-DANDROID_STL=c++_static' arguments '-DANDROID_STL=c++_static'
} }
} }
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
ndk {
debugSymbolLevel 'FULL'
}
} }
} }
externalNativeBuild { externalNativeBuild {
@ -37,4 +44,5 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-ads:19.1.0'
} }

View File

@ -1,36 +1,53 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.native_activity" package="com.kaliber.demo"
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- This .apk has no Java code itself, so set hasCode to false. -->
<application <application
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name">
android:hasCode="false">
<!-- Our activity is the built-in NativeActivity framework class. <activity
This will take care of integrating with our NDK code. --> android:name="com.google.android.gms.ads.AdActivity"
<activity android:name="android.app.NativeActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Translucent" />
<activity
android:name="com.kaliber.base.KaliberActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="portrait" android:screenOrientation="portrait"
android:configChanges="orientation|keyboardHidden"> android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="native-activity" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="kaliber" />
</activity> </activity>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-1321063817979967~1100949243" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.codepath.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
</manifest> </manifest>
<!-- END_INCLUDE(manifest) -->

View File

@ -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);
}
}
}

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">NativeActivity</string> <string name="app_name">demo</string>
<string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/1033173712</string>
</resources> </resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="files" path="." />
</paths>

View File

@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.2' classpath 'com.android.tools.build:gradle:4.1.0'
} }
} }

View File

@ -1,6 +1,6 @@
#Sun Feb 05 19:39:12 IST 2017 #Thu Oct 15 21:41:36 CEST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

View File

@ -4,5 +4,6 @@
# Location of the SDK. This is only used by Gradle. # Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the # For customization when using a Version Control System, please read the
# header note. # header note.
#Thu Apr 09 18:23:32 CEST 2020 #Fri Oct 16 12:48:26 CEST 2020
sdk.dir=/home/auygun/Android/Sdk ndk.dir=/home/auygun/spinning/Android/Sdk/ndk/21.3.6528147
sdk.dir=/home/auygun/spinning/Android/Sdk

View File

@ -68,6 +68,7 @@ GLTEST_SRC := \
$(SRC_ROOT)/base/collusion_test.cc \ $(SRC_ROOT)/base/collusion_test.cc \
$(SRC_ROOT)/base/log.cc \ $(SRC_ROOT)/base/log.cc \
$(SRC_ROOT)/base/random.cc \ $(SRC_ROOT)/base/random.cc \
$(SRC_ROOT)/base/sinc_resampler.cc \
$(SRC_ROOT)/base/task_runner.cc \ $(SRC_ROOT)/base/task_runner.cc \
$(SRC_ROOT)/base/timer.cc \ $(SRC_ROOT)/base/timer.cc \
$(SRC_ROOT)/base/vecmath.cc \ $(SRC_ROOT)/base/vecmath.cc \
@ -90,16 +91,17 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/image_quad.cc \ $(SRC_ROOT)/engine/image_quad.cc \
$(SRC_ROOT)/engine/image.cc \ $(SRC_ROOT)/engine/image.cc \
$(SRC_ROOT)/engine/mesh.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_linux.cc \
$(SRC_ROOT)/engine/platform/asset_file.cc \ $(SRC_ROOT)/engine/platform/asset_file.cc \
$(SRC_ROOT)/engine/platform/platform_base.cc \ $(SRC_ROOT)/engine/platform/platform_base.cc \
$(SRC_ROOT)/engine/platform/platform_linux.cc \ $(SRC_ROOT)/engine/platform/platform_linux.cc \
$(SRC_ROOT)/engine/renderer/geometry.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/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_types.cc \
$(SRC_ROOT)/engine/renderer/renderer.cc \
$(SRC_ROOT)/engine/renderer/shader.cc \ $(SRC_ROOT)/engine/renderer/shader.cc \
$(SRC_ROOT)/engine/renderer/texture.cc \ $(SRC_ROOT)/engine/renderer/texture.cc \
$(SRC_ROOT)/engine/shader_source.cc \ $(SRC_ROOT)/engine/shader_source.cc \
@ -108,8 +110,6 @@ GLTEST_SRC := \
$(SRC_ROOT)/engine/sound.cc \ $(SRC_ROOT)/engine/sound.cc \
$(SRC_ROOT)/third_party/glew/glew.c \ $(SRC_ROOT)/third_party/glew/glew.c \
$(SRC_ROOT)/third_party/jsoncpp/jsoncpp.cc \ $(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_internals.cc \
$(SRC_ROOT)/third_party/texture_compressor/dxt_encoder.cc \ $(SRC_ROOT)/third_party/texture_compressor/dxt_encoder.cc \
$(SRC_ROOT)/third_party/texture_compressor/texture_compressor_etc1.cc \ $(SRC_ROOT)/third_party/texture_compressor/texture_compressor_etc1.cc \

View File

@ -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

65
privacy.md Normal file
View File

@ -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.
**Childrens 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/)

View File

@ -12,6 +12,8 @@ source_set("base") {
"misc.h", "misc.h",
"random.cc", "random.cc",
"random.h", "random.h",
"sinc_resampler.cc",
"sinc_resampler.h",
"task_runner.cc", "task_runner.cc",
"task_runner.h", "task_runner.h",
"timer.cc", "timer.cc",

View File

@ -2,6 +2,7 @@
#define CLOSURE_H #define CLOSURE_H
#include <functional> #include <functional>
#include <memory>
#include <string> #include <string>
#include <tuple> #include <tuple>
@ -43,6 +44,16 @@ using Location = std::nullptr_t;
#endif #endif
// Bind a method to an object with a std::weak_ptr.
template <typename Class, typename ReturnType, typename... Args>
std::function<ReturnType(Args...)> BindWeak(ReturnType (Class::*func)(Args...),
std::weak_ptr<Class> weak_ptr) {
return [func, weak_ptr](Args... args) {
if (auto ptr = weak_ptr.lock())
std::invoke(func, ptr, args...);
};
}
} // namespace base } // namespace base
#endif // CLOSURE_H #endif // CLOSURE_H

View File

@ -6,24 +6,17 @@
#include <cstdio> #include <cstdio>
#endif #endif
#include <cstdlib> #include <cstdlib>
#include <mutex>
#include <unordered_map>
#include "vecmath.h"
namespace base { namespace base {
// Adapted from Chromium's logging implementation.
// This is never instantiated, it's just used for EAT_STREAM_PARAMETERS to have // 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 // an object of the correct type on the LHS of the unused part of the ternary
// operator. // 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; LogMessage::~LogMessage() {
void LogBase::Flush() {
stream_ << std::endl; stream_ << std::endl;
std::string text(stream_.str()); std::string text(stream_.str());
std::string filename(file_); std::string filename(file_);
@ -38,78 +31,31 @@ void LogBase::Flush() {
#endif #endif
} }
Log::Log(const char* file, int line) : LogBase(file, line) {} LogAbort LogAbort::Check(const char* file, int line, const char* expr) {
LogAbort instance(new LogMessage(file, line));
Log::~Log() { instance.GetLog().stream() << "CHECK: "
Flush(); << "(" << expr << ") ";
return instance;
} }
LogDiff::LogDiff(const char* file, int line) : LogBase(file, line) {} LogAbort LogAbort::DCheck(const char* file, int line, const char* expr) {
LogAbort instance(new LogMessage(file, line));
LogDiff::~LogDiff() { instance.GetLog().stream() << "DCHECK: "
static std::unordered_map<std::string, std::string> log_map; << "(" << expr << ") ";
static std::mutex lock; return instance;
auto key = std::string(file_) + std::to_string(line_);
bool flush = true;
{
std::lock_guard<std::mutex> 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) LogAbort LogAbort::NotReached(const char* file, int line) {
Flush(); LogAbort instance(new LogMessage(file, line));
instance.GetLog().stream() << "NOTREACHED ";
return instance;
} }
Check::Check(const char* file, LogAbort::LogAbort(LogMessage* log) : log_(log) {}
int line,
bool condition,
bool debug,
const char* expr)
: LogBase(file, line), condition_(condition) {
if (!condition_)
base() << (debug ? "DCHECK: (" : "CHECK: (") << expr << ") ";
}
Check::~Check() { LogAbort::~LogAbort() {
if (!condition_) { delete log_;
Flush();
std::abort(); std::abort();
} }
}
NotReached::NotReached(const char* file, int line) : LogBase(file, line) {
base() << "NOTREACHED ";
}
NotReached::~NotReached() {
Flush();
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 } // namespace base

View File

@ -3,114 +3,95 @@
#include <sstream> #include <sstream>
// Adapted from Chromium's logging implementation.
// Macros for logging that are active in both debug and release builds. The way // Macros for logging that are active in both debug and release builds. The way
// to log things is to stream things to LOG. // 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. // CHECK(condition) terminates the process if the condition is false.
// NOTREACHED annotates unreachable codepaths and terminates the process if // NOTREACHED annotates unreachable codepaths and terminates the process if
// reached. // reached.
#define LOG base::Log(__FILE__, __LINE__).base() #define LOG base::LogMessage(__FILE__, __LINE__).stream()
#define LOG_DIFF base::LogDiff(__FILE__, __LINE__).base() #define LOG_IF(condition) \
#define CHECK(expr) \ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
base::Check(__FILE__, __LINE__, static_cast<bool>(expr), false, #expr).base() #define CHECK(condition) \
#define NOTREACHED base::NotReached(__FILE__, __LINE__).base() 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. // Macros for logging which are active only in debug builds.
#ifdef _DEBUG #ifdef _DEBUG
#define DLOG base::Log(__FILE__, __LINE__).base() #define DLOG base::LogMessage(__FILE__, __LINE__).stream()
#define DLOG_DIFF base::LogDiff(__FILE__, __LINE__).base() #define DLOG_IF(condition) \
#define DCHECK(expr) \ LAZY_STREAM(condition, base::LogMessage(__FILE__, __LINE__).stream())
base::Check(__FILE__, __LINE__, static_cast<bool>(expr), true, #expr).base() #define DCHECK(condition) \
LAZY_STREAM(!(condition), \
base::LogAbort::DCheck(__FILE__, __LINE__, #condition) \
.GetLog() \
.stream())
#else #else
// "debug mode" logging is compiled away to nothing for release builds. // "debug mode" logging is compiled away to nothing for release builds.
#define DLOG EAT_STREAM_PARAMETERS #define DLOG EAT_STREAM_PARAMETERS
#define DLOG_DIFF EAT_STREAM_PARAMETERS #define DLOG_IF(condition) EAT_STREAM_PARAMETERS
#define DCHECK(expr) EAT_STREAM_PARAMETERS #define DCHECK(condition) EAT_STREAM_PARAMETERS
#endif #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. // Avoid any pointless instructions to be emitted by the compiler.
#define EAT_STREAM_PARAMETERS \ #define EAT_STREAM_PARAMETERS \
true ? (void)0 : base::LogBase::Voidify() & (*base::LogBase::swallow_stream) LAZY_STREAM(false, *base::LogMessage::swallow_stream)
namespace base { namespace base {
struct Vector2; class LogMessage {
struct Vector3;
struct Vector4;
class LogBase {
public: public:
class Voidify { class Voidify {
public: public:
Voidify() = default; Voidify() = default;
// This has to be an operator with a precedence lower than << but // This has to be an operator with a precedence lower than << but
// higher than ?: // 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_; } std::ostream& stream() { return stream_; }
static LogBase* swallow_stream; static std::ostream* swallow_stream;
protected: protected:
const char* file_; const char* file_;
const int line_; const int line_;
std::ostringstream stream_; std::ostringstream stream_;
LogBase(const char* file, int line);
~LogBase();
void Flush();
}; };
class Log : public LogBase { class LogAbort {
public: public:
Log(const char* file, int line); ~LogAbort();
~Log();
};
class LogDiff : public LogBase { static LogAbort Check(const char* file, int line, const char* expr);
public: static LogAbort DCheck(const char* file, int line, const char* expr);
LogDiff(const char* file, int line); static LogAbort NotReached(const char* file, int line);
~LogDiff();
};
class Check : public LogBase { LogMessage& GetLog() { return *log_; }
public:
Check(const char* file,
int line,
bool condition,
bool debug,
const char* expr);
~Check();
private: private:
bool condition_; LogMessage* log_;
LogAbort(LogMessage* log);
}; };
class NotReached : public LogBase {
public:
NotReached(const char* file, int line);
~NotReached();
};
template <typename T>
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 } // namespace base
#endif // LOG_H #endif // LOG_H

View File

@ -3,24 +3,35 @@
#include <limits> #include <limits>
#include "interpolation.h" #include "interpolation.h"
#include "log.h"
namespace base { namespace base {
Random::Random() { Random::Random() {
std::random_device rd; std::random_device rd;
generator_ = std::mt19937(rd()); seed_ = rd();
real_distribution_ = std::uniform_real_distribution<float>(0, 1); DLOG << "Random seed: " << seed_;
Initialize();
} }
Random::Random(unsigned seed) { Random::Random(unsigned seed) {
generator_ = std::mt19937(seed); seed_ = seed;
real_distribution_ = std::uniform_real_distribution<float>(0, 1); Initialize();
} }
Random::~Random() = default; Random::~Random() = default;
float Random::GetFloat() {
return real_distribution_(generator_);
}
int Random::Roll(int sides) { int Random::Roll(int sides) {
return Lerp(1, sides, GetFloat()); return Lerp(1, sides, GetFloat());
} }
void Random::Initialize() {
generator_ = std::mt19937(seed_);
real_distribution_ = std::uniform_real_distribution<float>(0, 1);
}
} // namespace base } // namespace base

View File

@ -12,14 +12,19 @@ class Random {
~Random(); ~Random();
// Returns a random float between 0 and 1. // Returns a random float between 0 and 1.
float GetFloat() { return real_distribution_(generator_); } float GetFloat();
// Roll dice with the given number of sides. // Roll dice with the given number of sides.
int Roll(int sides); int Roll(int sides);
unsigned seed() const { return seed_; }
private: private:
unsigned seed_ = 0;
std::mt19937 generator_; std::mt19937 generator_;
std::uniform_real_distribution<float> real_distribution_; std::uniform_real_distribution<float> real_distribution_;
void Initialize();
}; };
} // namespace base } // namespace base

View File

@ -4,6 +4,8 @@
#include <condition_variable> #include <condition_variable>
#include <mutex> #include <mutex>
#include "../base/log.h"
namespace base { namespace base {
class Semaphore { class Semaphore {
@ -14,6 +16,7 @@ class Semaphore {
std::unique_lock<std::mutex> scoped_lock(mutex_); std::unique_lock<std::mutex> scoped_lock(mutex_);
cv_.wait(scoped_lock, [&]() { return count_ > 0; }); cv_.wait(scoped_lock, [&]() { return count_ > 0; });
--count_; --count_;
DCHECK(count_ >= 0);
} }
void Release() { void Release() {

446
src/base/sinc_resampler.cc Normal file
View File

@ -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 <cmath>
#include <cstring>
#include <limits>
#include "log.h"
#if defined(_M_X64) || defined(__x86_64__) || defined(__i386__)
#include <xmmintrin.h>
#define CONVOLVE_FUNC Convolve_SSE
#elif defined(_M_ARM64) || defined(__aarch64__)
#include <arm_neon.h>
#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<float*>(
base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
kernel_pre_sinc_storage_(static_cast<float*>(
base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
kernel_window_storage_(static_cast<float*>(
base::AlignedAlloc<16>(sizeof(float) * kKernelStorageSize))),
input_buffer_(static_cast<float*>(
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<float>(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<float>(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<float>(
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<double>::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<float>(
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<int>(virtual_source_idx_);
const double virtual_offset_idx =
(virtual_source_idx_ - source_idx) * kKernelOffsetCount;
const int offset_idx = static_cast<int>(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<uintptr_t>(k1) & 0x0F));
DCHECK(0u == (reinterpret_cast<uintptr_t>(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<int>(
std::ceil(static_cast<float>(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<float>((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<uintptr_t>(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<float>(1.0 - kernel_interpolation_factor)));
m_sums2 = _mm_mul_ps(
m_sums2, _mm_set_ps1(static_cast<float>(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

154
src/base/sinc_resampler.h Normal file
View File

@ -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 <functional>
#include <memory>
#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<void(int frames, float* destination)> 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<float[]> kernel_storage_;
base::AlignedMemPtr<float[]> kernel_pre_sinc_storage_;
base::AlignedMemPtr<float[]> kernel_window_storage_;
// Data from the source is copied into this buffer for each processing pass.
base::AlignedMemPtr<float[]> 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_

View File

@ -4,14 +4,14 @@
namespace { namespace {
void EnqueueTaskAndReplyRelay(const base::Location& from, void PostTaskAndReplyRelay(base::Location from,
base::Closure task_cb, base::Closure task_cb,
base::Closure reply_cb, base::Closure reply_cb,
base::TaskRunner* destination) { base::TaskRunner* destination) {
task_cb(); task_cb();
if (reply_cb) if (reply_cb)
destination->EnqueueTask(from, std::move(reply_cb)); destination->PostTask(from, std::move(reply_cb));
} }
} // namespace } // namespace
@ -30,25 +30,24 @@ TaskRunner* TaskRunner::GetThreadLocalTaskRunner() {
return thread_local_task_runner.get(); 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); DCHECK(task) << LOCATION(from);
task_count_.fetch_add(1, std::memory_order_relaxed);
std::lock_guard<std::mutex> scoped_lock(lock_); std::lock_guard<std::mutex> scoped_lock(lock_);
queue_.emplace_back(from, std::move(task)); queue_.emplace_back(from, std::move(task));
} }
void TaskRunner::EnqueueTaskAndReply(const Location& from, void TaskRunner::PostTaskAndReply(const Location& from,
Closure task, Closure task,
Closure reply) { Closure reply) {
DCHECK(task) << LOCATION(from); DCHECK(task) << LOCATION(from);
DCHECK(reply) << LOCATION(from); DCHECK(reply) << LOCATION(from);
DCHECK(thread_local_task_runner) << 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::move(reply), thread_local_task_runner.get());
PostTask(from, std::move(relay));
std::lock_guard<std::mutex> scoped_lock(lock_);
queue_.emplace_back(from, std::move(relay));
} }
void TaskRunner::MultiConsumerRun() { void TaskRunner::MultiConsumerRun() {
@ -69,6 +68,7 @@ void TaskRunner::MultiConsumerRun() {
#endif #endif
task_cb(); task_cb();
task_count_.fetch_sub(1, std::memory_order_relaxed);
} }
} }
@ -90,7 +90,16 @@ void TaskRunner::SingleConsumerRun() {
#endif #endif
task_cb(); task_cb();
task_count_.fetch_sub(1, std::memory_order_relaxed);
} }
cv_.notify_one();
}
void TaskRunner::WaitForCompletion() {
std::unique_lock<std::mutex> scoped_lock(lock_);
cv_.wait(scoped_lock, [&]() -> bool {
return task_count_.load(std::memory_order_relaxed) == 0;
});
} }
} // namespace base } // namespace base

View File

@ -1,6 +1,8 @@
#ifndef TASK_RUNNER_H #ifndef TASK_RUNNER_H
#define TASK_RUNNER_H #define TASK_RUNNER_H
#include <atomic>
#include <condition_variable>
#include <deque> #include <deque>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -25,7 +27,7 @@ void ReturnAsParamAdapter(std::function<ReturnType()> func,
template <typename ReturnType> template <typename ReturnType>
void ReplyAdapter(std::function<void(ReturnType)> callback, void ReplyAdapter(std::function<void(ReturnType)> callback,
ReturnType* result) { ReturnType* result) {
callback(std::move(*result)); callback(*result);
delete result; delete result;
} }
@ -41,16 +43,16 @@ class TaskRunner {
TaskRunner() = default; TaskRunner() = default;
~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 <typename ReturnType> template <typename ReturnType>
void EnqueueTaskAndReplyWithResult(const Location& from, void PostTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task, std::function<ReturnType()> task,
std::function<void(ReturnType)> reply) { std::function<void(ReturnType)> reply) {
auto* result = new ReturnType; auto* result = new ReturnType;
return EnqueueTaskAndReply( return PostTaskAndReply(
from, from,
std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task), std::bind(internal::ReturnAsParamAdapter<ReturnType>, std::move(task),
result), result),
@ -62,6 +64,8 @@ class TaskRunner {
void SingleConsumerRun(); void SingleConsumerRun();
void WaitForCompletion();
static void CreateThreadLocalTaskRunner(); static void CreateThreadLocalTaskRunner();
static TaskRunner* GetThreadLocalTaskRunner(); static TaskRunner* GetThreadLocalTaskRunner();
@ -70,6 +74,8 @@ class TaskRunner {
std::deque<Task> queue_; std::deque<Task> queue_;
mutable std::mutex lock_; mutable std::mutex lock_;
std::condition_variable cv_;
std::atomic<size_t> task_count_{0};
static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner; static thread_local std::unique_ptr<TaskRunner> thread_local_task_runner;

View File

@ -1,5 +1,7 @@
#include "timer.h" #include "timer.h"
#include <thread>
namespace base { namespace base {
Timer::Timer() { Timer::Timer() {
@ -25,4 +27,20 @@ void Timer::Update() {
seconds_accumulated_ += seconds_passed_; 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 } // namespace base

View File

@ -14,6 +14,8 @@ class Timer {
void Update(); void Update();
static void Sleep(float duration);
float GetSecondsPassed() const { return seconds_passed_; } float GetSecondsPassed() const { return seconds_passed_; }
float GetSecondsAccumulated() const { return seconds_accumulated_; } float GetSecondsAccumulated() const { return seconds_accumulated_; }

View File

@ -1,7 +1,13 @@
#include "vecmath.h" #include "vecmath.h"
using namespace std::string_literals;
namespace base { 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 Ortho(float left, float right, float bottom, float top) {
Matrix4x4 m(1); Matrix4x4 m(1);
m.c[0].x = 2.0f / (right - left); m.c[0].x = 2.0f / (right - left);

View File

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <string>
namespace base { namespace base {
@ -64,6 +65,8 @@ struct Vector2 {
} }
const float* GetData() const { return &x; } const float* GetData() const { return &x; }
std::string ToString();
}; };
inline Vector2 operator+(const Vector2& v1, const Vector2& v2) { inline Vector2 operator+(const Vector2& v1, const Vector2& v2) {
@ -130,7 +133,7 @@ struct Vector4 {
}; };
inline Vector4 operator*(const Vector4& v1, const Vector4& v2) { 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) { inline Vector4 operator*(const Vector4& v, float s) {

View File

@ -40,19 +40,19 @@ void Worker::Shutdown() {
threads_.clear(); threads_.clear();
} }
void Worker::EnqueueTask(const Location& from, Closure task) { void Worker::PostTask(const Location& from, Closure task) {
DCHECK((!threads_.empty())); DCHECK((!threads_.empty()));
task_runner_.EnqueueTask(from, std::move(task)); task_runner_.PostTask(from, std::move(task));
semaphore_.Release(); semaphore_.Release();
} }
void Worker::EnqueueTaskAndReply(const Location& from, void Worker::PostTaskAndReply(const Location& from,
Closure task, Closure task,
Closure reply) { Closure reply) {
DCHECK((!threads_.empty())); 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(); semaphore_.Release();
} }

View File

@ -26,15 +26,15 @@ class Worker {
void Shutdown(); 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 <typename ReturnType> template <typename ReturnType>
void EnqueueTaskAndReplyWithResult(const Location& from, void PostTaskAndReplyWithResult(const Location& from,
std::function<ReturnType()> task, std::function<ReturnType()> task,
std::function<void(ReturnType)> reply) { std::function<void(ReturnType)> reply) {
task_runner_.EnqueueTaskAndReplyWithResult(from, std::move(task), task_runner_.PostTaskAndReplyWithResult(from, std::move(task),
std::move(reply)); std::move(reply));
semaphore_.Release(); semaphore_.Release();
} }

View File

@ -47,10 +47,6 @@ bool Credits::Initialize() {
return true; return true;
} }
void Credits::Update(float delta_time) {
text_animator_.Update(delta_time);
}
void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) { void Credits::OnInputEvent(std::unique_ptr<InputEvent> event) {
if ((event->GetType() == InputEvent::kDragEnd || if ((event->GetType() == InputEvent::kDragEnd ||
event->GetType() == InputEvent::kNavigateBack) && event->GetType() == InputEvent::kNavigateBack) &&
@ -67,19 +63,19 @@ void Credits::Show() {
for (int i = 0; i < kNumLines; ++i) { for (int i = 0; i < kNumLines; ++i) {
text_[i].Create("credits", {1, kNumLines}); text_[i].Create("credits", {1, kNumLines});
text_[i].SetZOrder(50); 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].SetColor(kTextColor * Vector4(1, 1, 1, 0));
text_[i].SetFrame(i); text_[i].SetFrame(i);
if (i > 0) { if (i > 0) {
text_[i].PlaceToBottomOf(text_[i - 1]); text_[i].PlaceToBottomOf(text_[i - 1]);
text_[i].Translate(text_[i - 1].GetOffset() * Vector2(0, 1)); text_[i].Translate(text_[i - 1].GetPosition() * Vector2(0, 1));
text_[i].Translate({0, text_[i - 1].GetScale().y * -kLineSpaces[i - 1]}); text_[i].Translate({0, text_[i - 1].GetSize().y * -kLineSpaces[i - 1]});
} }
} }
float center_offset_y = 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) for (int i = 0; i < kNumLines; ++i)
text_[i].Translate({0, center_offset_y}); text_[i].Translate({0, center_offset_y});

View File

@ -21,8 +21,6 @@ class Credits {
bool Initialize(); bool Initialize();
void Update(float delta_time);
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void Show(); void Show();

View File

@ -83,9 +83,9 @@ void Demo::Update(float delta_time) {
hud_.SetScore(score_, true); hud_.SetScore(score_, true);
} }
hud_.Update(delta_time); sky_.Update(delta_time);
menu_.Update(delta_time); player_.Update(delta_time);
credits_.Update(delta_time); enemy_.Update(delta_time);
if (state_ == kMenu) if (state_ == kMenu)
UpdateMenuState(delta_time); UpdateMenuState(delta_time);
@ -102,7 +102,7 @@ void Demo::LostFocus() {
EnterMenuState(); EnterMenuState();
} }
void Demo::GainedFocus() {} void Demo::GainedFocus(bool from_interstitial_ad) {}
void Demo::AddScore(int score) { void Demo::AddScore(int score) {
add_score_ += score; add_score_ += score;
@ -111,6 +111,13 @@ void Demo::AddScore(int score) {
void Demo::EnterMenuState() { void Demo::EnterMenuState() {
if (state_ == kMenu) if (state_ == kMenu)
return; return;
if (state_ == kState_Invalid || state_ == kGame) {
sky_.SetSpeed(0);
player_.Pause(true);
enemy_.Pause(true);
}
if (wave_ == 0) { if (wave_ == 0) {
menu_.SetOptionEnabled(Menu::kContinue, false); menu_.SetOptionEnabled(Menu::kContinue, false);
} else { } else {
@ -131,7 +138,11 @@ void Demo::EnterCreditsState() {
void Demo::EnterGameState() { void Demo::EnterGameState() {
if (state_ == kGame) if (state_ == kGame)
return; return;
sky_.SetSpeed(0.04f);
hud_.Show(); hud_.Show();
player_.Pause(false);
enemy_.Pause(false);
state_ = kGame; state_ = kGame;
} }
@ -160,10 +171,6 @@ void Demo::UpdateMenuState(float delta_time) {
} }
void Demo::UpdateGameState(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_) if (waiting_for_next_wave_)
return; return;

View File

@ -24,7 +24,7 @@ class Demo : public eng::Game {
void LostFocus() override; void LostFocus() override;
void GainedFocus() override; void GainedFocus(bool from_interstitial_ad) override;
void AddScore(int score); void AddScore(int score);

View File

@ -57,7 +57,7 @@ bool Enemy::Initialize() {
} }
void Enemy::Update(float delta_time) { void Enemy::Update(float delta_time) {
if (!waiting_for_next_wave_) { if (!waiting_for_next_wave_ && !paused_) {
if (spawn_factor_interpolator_ < 1) { if (spawn_factor_interpolator_ < 1) {
spawn_factor_interpolator_ += delta_time * 0.1f; spawn_factor_interpolator_ += delta_time * 0.1f;
if (spawn_factor_interpolator_ > 1) if (spawn_factor_interpolator_ > 1)
@ -70,17 +70,23 @@ void Enemy::Update(float delta_time) {
SpawnNextEnemy(); SpawnNextEnemy();
} }
for (auto it = enemies_.begin(); it != enemies_.end(); ++it) { for (auto it = enemies_.begin(); it != enemies_.end();) {
if (it->marked_for_removal) { if (it->marked_for_removal)
it = enemies_.erase(it); it = enemies_.erase(it);
continue; else
++it;
} }
it->sprite_animator.Update(delta_time); }
it->target_animator.Update(delta_time);
it->blast_animator.Update(delta_time); void Enemy::Pause(bool pause) {
it->health_animator.Update(delta_time); paused_ = pause;
it->score_animator.Update(delta_time); for (auto& e : enemies_) {
it->movement_animator.Update(delta_time); 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); EnemyUnit* target = GetTarget(damage_type);
if (target) if (target)
return target->sprite.GetOffset() - return target->sprite.GetPosition() -
Vector2(0, target->sprite.GetScale().y / 2.5f); Vector2(0, target->sprite.GetSize().y / 2.5f);
return {0, 0}; return {0, 0};
} }
@ -122,11 +128,11 @@ void Enemy::SelectTarget(DamageType damage_type,
e.target_animator.Stop(Animator::kAllAnimations); e.target_animator.Stop(Animator::kAllAnimations);
} }
if (!base::Intersection(e.sprite.GetOffset(), if (!base::Intersection(e.sprite.GetPosition(),
e.sprite.GetScale() * snap_factor, origin, dir)) e.sprite.GetSize() * snap_factor, origin, dir))
continue; 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(); float enemy_weapon_dist = weapon_enemy_dir.Magnitude();
if (closest_dist > enemy_weapon_dist) { if (closest_dist > enemy_weapon_dist) {
closest_dist = enemy_weapon_dist; closest_dist = enemy_weapon_dist;
@ -232,10 +238,10 @@ void Enemy::TakeDamage(EnemyUnit* target, int damage) {
} else { } else {
target->targetted_by_weapon_ = kDamageType_Invalid; 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; s.x *= (float)target->hit_points / (float)target->total_health;
float t = (s.x - target->health_bar.GetScale().x) / 2; float t = (s.x - target->health_bar.GetSize().x) / 2;
target->health_bar.SetScale(s); target->health_bar.SetSize(s);
target->health_bar.Translate({t, 0}); target->health_bar.Translate({t, 0});
target->health_base.SetVisible(true); target->health_base.SetVisible(true);
@ -312,8 +318,8 @@ void Enemy::Spawn(EnemyType enemy_type,
} }
e.sprite.SetZOrder(11); e.sprite.SetZOrder(11);
e.sprite.SetVisible(true); e.sprite.SetVisible(true);
Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetScale().y / 2); Vector2 spawn_pos = pos + Vector2(0, e.sprite.GetSize().y / 2);
e.sprite.SetOffset(spawn_pos); e.sprite.SetPosition(spawn_pos);
e.sprite.SetFrame(enemy_frame_start[enemy_type][damage_type]); e.sprite.SetFrame(enemy_frame_start[enemy_type][damage_type]);
e.sprite_animator.SetFrames(enemy_frame_count[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.Create("target_tex", {6, 2});
e.target.SetZOrder(12); e.target.SetZOrder(12);
e.target.SetOffset(spawn_pos); e.target.SetPosition(spawn_pos);
e.blast.Create("blast_tex", {6, 2}); e.blast.Create("blast_tex", {6, 2});
e.blast.SetZOrder(12); e.blast.SetZOrder(12);
e.blast.SetOffset(spawn_pos); e.blast.SetPosition(spawn_pos);
e.health_base.SetZOrder(11); e.health_base.SetZOrder(11);
e.health_base.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f)); e.health_base.SetSize(e.sprite.GetSize() * Vector2(0.6f, 0.01f));
e.health_base.SetOffset(spawn_pos); e.health_base.SetPosition(spawn_pos);
e.health_base.PlaceToBottomOf(e.sprite); e.health_base.PlaceToBottomOf(e.sprite);
e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1}); e.health_base.SetColor({0.5f, 0.5f, 0.5f, 1});
e.health_bar.SetZOrder(11); e.health_bar.SetZOrder(11);
e.health_bar.Scale(e.sprite.GetScale() * Vector2(0.6f, 0.01f)); e.health_bar.SetSize(e.sprite.GetSize() * Vector2(0.6f, 0.01f));
e.health_bar.SetOffset(spawn_pos); e.health_bar.SetPosition(spawn_pos);
e.health_bar.PlaceToBottomOf(e.sprite); e.health_bar.PlaceToBottomOf(e.sprite);
e.health_bar.SetColor({0.161f, 0.89f, 0.322f, 1}); 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.Create("score_tex"s + std::to_string(e.enemy_type));
e.score.SetZOrder(12); e.score.SetZOrder(12);
e.score.SetColor({1, 1, 1, 1}); e.score.SetColor({1, 1, 1, 1});
e.score.SetOffset(spawn_pos); e.score.SetPosition(spawn_pos);
e.target_animator.Attach(&e.target); e.target_animator.Attach(&e.target);

View File

@ -26,6 +26,8 @@ class Enemy {
void Update(float delta_time); void Update(float delta_time);
void Pause(bool pause);
bool HasTarget(DamageType damage_type); bool HasTarget(DamageType damage_type);
base::Vector2 GetTargetPos(DamageType damage_type); base::Vector2 GetTargetPos(DamageType damage_type);
@ -87,6 +89,8 @@ class Enemy {
int last_spawn_col_ = 0; int last_spawn_col_ = 0;
bool paused_ = false;
void TakeDamage(EnemyUnit* target, int damage); void TakeDamage(EnemyUnit* target, int damage);
void SpawnNextEnemy(); void SpawnNextEnemy();

View File

@ -44,19 +44,19 @@ bool Hud::Initialize() {
text_[i].SetZOrder(30); text_[i].SetZOrder(30);
text_[i].SetColor(kTextColor * Vector4(1, 1, 1, 0)); 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); pos -= engine.GetScreenSize() * Vector2(kHorizontalMargin, kVerticalMargin);
Vector2 scale = engine.GetScreenSize() * Vector2(1, 0); Vector2 scale = engine.GetScreenSize() * Vector2(1, 0);
scale -= engine.GetScreenSize() * Vector2(kHorizontalMargin * 4, 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].SetZOrder(30);
progress_bar_[i].Scale(scale); progress_bar_[i].SetSize(scale);
progress_bar_[i].Translate(pos * Vector2(0, 1)); progress_bar_[i].Translate(pos * Vector2(0, 1));
progress_bar_[i].SetColor(kPprogressBarColor[i] * Vector4(1, 1, 1, 0)); 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)); text_[i].Translate(pos * Vector2(i ? 1 : -1, 1));
progress_bar_animator_[i].Attach(&progress_bar_[i]); progress_bar_animator_[i].Attach(&progress_bar_[i]);
@ -73,13 +73,6 @@ bool Hud::Initialize() {
return true; 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() { void Hud::Show() {
if (text_[0].IsVisible()) if (text_[0].IsVisible())
return; return;
@ -118,9 +111,9 @@ void Hud::SetWave(int wave, bool flash) {
void Hud::SetProgress(float progress) { void Hud::SetProgress(float progress) {
progress = std::min(std::max(0.0f, progress), 1.0f); progress = std::min(std::max(0.0f, progress), 1.0f);
last_progress_ = progress; last_progress_ = progress;
Vector2 s = progress_bar_[0].GetScale() * Vector2(progress, 1); Vector2 s = progress_bar_[0].GetSize() * Vector2(progress, 1);
float t = (s.x - progress_bar_[1].GetScale().x) / 2; float t = (s.x - progress_bar_[1].GetSize().x) / 2;
progress_bar_[1].SetScale(s); progress_bar_[1].SetSize(s);
progress_bar_[1].Translate({t, 0}); progress_bar_[1].Translate({t, 0});
} }

View File

@ -20,8 +20,6 @@ class Hud {
bool Initialize(); bool Initialize();
void Update(float delta_time);
void Show(); void Show();
void SetScore(int score, bool flash); void SetScore(int score, bool flash);

View File

@ -75,19 +75,11 @@ bool Menu::Initialize() {
return true; 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<InputEvent> event) { void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
if (event->GetType() == InputEvent::kDragStart) 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) else if (event->GetType() == InputEvent::kDrag)
tap_pos_[1] = event->GetVector(0); tap_pos_[1] = event->GetVector();
if (event->GetType() != InputEvent::kDragEnd || IsAnimating()) if (event->GetType() != InputEvent::kDragEnd || IsAnimating())
return; return;
@ -95,13 +87,11 @@ void Menu::OnInputEvent(std::unique_ptr<InputEvent> event) {
for (int i = 0; i < kOption_Max; ++i) { for (int i = 0; i < kOption_Max; ++i) {
if (items_[i].hide) if (items_[i].hide)
continue; continue;
if (!Intersection(items_[i].text.GetOffset(), if (!Intersection(items_[i].text.GetPosition(),
items_[i].text.GetScale() * Vector2(1.2f, 2), items_[i].text.GetSize() * Vector2(1.2f, 2), tap_pos_[0]))
tap_pos_[0]))
continue; continue;
if (!Intersection(items_[i].text.GetOffset(), if (!Intersection(items_[i].text.GetPosition(),
items_[i].text.GetScale() * Vector2(1.2f, 2), items_[i].text.GetSize() * Vector2(1.2f, 2), tap_pos_[1]))
tap_pos_[1]))
continue; continue;
items_[i].text_animator.SetEndCallback(Animator::kBlending, items_[i].text_animator.SetEndCallback(Animator::kBlending,
@ -117,12 +107,13 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
if (i == o) if (i == o)
items_[i].hide = !enable; items_[i].hide = !enable;
if (!items_[i].hide) { if (!items_[i].hide) {
items_[i].text.SetOffset({0, 0}); items_[i].text.SetPosition({0, 0});
if (last >= 0) { if (last >= 0) {
items_[i].text.PlaceToBottomOf(items_[last].text); 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( items_[i].text.Translate(
{0, items_[last].text.GetScale().y * -kMenuOptionSpace}); {0, items_[last].text.GetSize().y * -kMenuOptionSpace});
} }
if (first < 0) if (first < 0)
first = i; first = i;
@ -131,7 +122,8 @@ void Menu::SetOptionEnabled(Option o, bool enable) {
} }
float center_offset_y = 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) { for (int i = 0; i < kOption_Max; ++i) {
if (!items_[i].hide) if (!items_[i].hide)
items_[i].text.Translate({0, center_offset_y}); items_[i].text.Translate({0, center_offset_y});

View File

@ -30,8 +30,6 @@ class Menu {
bool Initialize(); bool Initialize();
void Update(float delta_time);
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
void SetOptionEnabled(Option o, bool enable); void SetOptionEnabled(Option o, bool enable);

View File

@ -31,24 +31,26 @@ bool Player::Initialize() {
} }
void Player::Update(float delta_time) { 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) if (active_weapon_ != kDamageType_Invalid)
UpdateTarget(); 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<InputEvent> event) { void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
if (event->GetType() == InputEvent::kNavigateBack) if (event->GetType() == InputEvent::kNavigateBack)
NavigateBack(); NavigateBack();
else if (event->GetType() == InputEvent::kDragStart) else if (event->GetType() == InputEvent::kDragStart)
DragStart(event->GetVector(0)); DragStart(event->GetVector());
else if (event->GetType() == InputEvent::kDrag) else if (event->GetType() == InputEvent::kDrag)
Drag(event->GetVector(0)); Drag(event->GetVector());
else if (event->GetType() == InputEvent::kDragEnd) else if (event->GetType() == InputEvent::kDragEnd)
DragEnd(); DragEnd();
else if (event->GetType() == InputEvent::kDragCancel) else if (event->GetType() == InputEvent::kDragCancel)
@ -58,18 +60,18 @@ void Player::OnInputEvent(std::unique_ptr<InputEvent> event) {
Vector2 Player::GetWeaponPos(DamageType type) const { Vector2 Player::GetWeaponPos(DamageType type) const {
return Engine::Get().GetScreenSize() / return Engine::Get().GetScreenSize() /
Vector2(type == kDamageType_Green ? 3.5f : -3.5f, -2) + 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 { Vector2 Player::GetWeaponScale() const {
return weapon_[0].GetScale(); return weapon_[0].GetSize();
} }
DamageType Player::GetWeaponType(const Vector2& pos) { DamageType Player::GetWeaponType(const Vector2& pos) {
DamageType closest_weapon = kDamageType_Invalid; DamageType closest_weapon = kDamageType_Invalid;
float closest_dist = std::numeric_limits<float>::max(); float closest_dist = std::numeric_limits<float>::max();
for (int i = 0; i < 2; ++i) { 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) { if (dist < closest_dist) {
closest_dist = dist; closest_dist = dist;
closest_weapon = (DamageType)i; closest_weapon = (DamageType)i;
@ -77,20 +79,11 @@ DamageType Player::GetWeaponType(const Vector2& pos) {
} }
DCHECK(closest_weapon != kDamageType_Invalid); 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 closest_weapon;
return kDamageType_Invalid; 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) { void Player::WarmupWeapon(DamageType type) {
cooldown_animator_[type].Stop(Animator::kFrames); cooldown_animator_[type].Stop(Animator::kFrames);
warmup_animator_[type].Play(Animator::kFrames, false); warmup_animator_[type].Play(Animator::kFrames, false);
@ -106,25 +99,27 @@ void Player::Fire(DamageType type, Vector2 dir) {
Enemy& enemy = static_cast<Demo*>(engine.GetGame())->GetEnemy(); Enemy& enemy = static_cast<Demo*>(engine.GetGame())->GetEnemy();
if (enemy.HasTarget(type)) if (enemy.HasTarget(type))
dir = weapon_[type].GetOffset() - enemy.GetTargetPos(type); dir = weapon_[type].GetPosition() - enemy.GetTargetPos(type);
else else
dir *= engine.GetScreenSize().y * 1.3f; dir *= engine.GetScreenSize().y * 1.3f;
float len = dir.Magnitude(); float len = dir.Magnitude();
SetBeamLength(type, len); beam_[type].SetSize({len, beam_[type].GetSize().y});
beam_[type].SetPosition(weapon_[type].GetPosition());
dir.Normalize(); dir.Normalize();
float cos_theta = dir.DotProduct(Vector2(1, 0)); float cos_theta = dir.DotProduct(Vector2(1, 0));
float theta = acos(cos_theta) + M_PI; float theta = acos(cos_theta) + M_PI;
beam_[type].SetTheta(theta); 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].SetColor({1, 1, 1, 1});
beam_[type].SetVisible(true); beam_[type].SetVisible(true);
beam_spark_[type].SetVisible(true); beam_spark_[type].SetVisible(true);
spark_animator_[type].Stop(Animator::kMovement); 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; Vector2 movement = dir * -length;
// Convert from units per second to duration. // Convert from units per second to duration.
float speed = 1.0f / (18.0f / length); float speed = 1.0f / (18.0f / length);
@ -144,33 +139,27 @@ void Player::SetupWeapons() {
drag_sign_[i].SetZOrder(21); drag_sign_[i].SetZOrder(21);
drag_sign_[i].SetFrame(i * 8); drag_sign_[i].SetFrame(i * 8);
Vector2 pos = GetWeaponPos((DamageType)i);
// Setup weapon. // Setup weapon.
weapon_[i].Create("weapon_tex", {8, 2}); weapon_[i].Create("weapon_tex", {8, 2});
weapon_[i].SetZOrder(24); weapon_[i].SetZOrder(24);
weapon_[i].SetVisible(true); weapon_[i].SetVisible(true);
weapon_[i].SetFrame(wepon_warmup_frame[i]); weapon_[i].SetFrame(wepon_warmup_frame[i]);
weapon_[i].SetPosition(pos);
// Setup beam. // Setup beam.
beam_[i].Create("beam_tex", {1, 2}); beam_[i].Create("beam_tex", {1, 2});
beam_[i].SetZOrder(22); beam_[i].SetZOrder(22);
beam_[i].SetFrame(i); beam_[i].SetFrame(i);
beam_[i].PlaceToRightOf(weapon_[i]); beam_[i].SetPosition(pos);
beam_[i].Translate(weapon_[i].GetScale() * Vector2(-0.5f, 0)); beam_[i].Translate(beam_[i].GetSize() * Vector2(-0.5f, -0.5f));
beam_[i].SetPivot(beam_[i].GetOffset());
// Setup beam spark. // Setup beam spark.
beam_spark_[i].Create("weapon_tex", {8, 2}); beam_spark_[i].Create("weapon_tex", {8, 2});
beam_spark_[i].SetZOrder(23); beam_spark_[i].SetZOrder(23);
beam_spark_[i].SetFrame(i * 8 + 1); beam_spark_[i].SetFrame(i * 8 + 1);
beam_spark_[i].PlaceToRightOf(weapon_[i]); beam_spark_[i].SetPosition(pos);
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);
// Setup animators. // Setup animators.
weapon_[i].SetFrame(wepon_cooldown_frame[i]); weapon_[i].SetFrame(wepon_cooldown_frame[i]);
@ -227,7 +216,7 @@ void Player::DragStart(const Vector2& pos) {
drag_start_ = drag_end_ = 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); drag_sign_[active_weapon_].SetVisible(true);
} }
@ -236,7 +225,7 @@ void Player::Drag(const Vector2& pos) {
return; return;
drag_end_ = pos; drag_end_ = pos;
drag_sign_[active_weapon_].SetOffset(drag_end_); drag_sign_[active_weapon_].SetPosition(drag_end_);
if (ValidateDrag()) { if (ValidateDrag()) {
if (!drag_valid_ && !IsFiring(active_weapon_)) if (!drag_valid_ && !IsFiring(active_weapon_))
@ -307,7 +296,7 @@ bool Player::ValidateDrag() {
Vector2 dir = drag_end_ - drag_start_; Vector2 dir = drag_end_ - drag_start_;
float len = dir.Magnitude(); float len = dir.Magnitude();
dir.Normalize(); dir.Normalize();
if (len < weapon_[active_weapon_].GetScale().y / 4) if (len < weapon_[active_weapon_].GetSize().y / 4)
return false; return false;
if (dir.DotProduct(Vector2(0, 1)) < 0) if (dir.DotProduct(Vector2(0, 1)) < 0)
return false; return false;

View File

@ -21,6 +21,8 @@ class Player {
void Update(float delta_time); void Update(float delta_time);
void Pause(bool pause);
void OnInputEvent(std::unique_ptr<eng::InputEvent> event); void OnInputEvent(std::unique_ptr<eng::InputEvent> event);
base::Vector2 GetWeaponPos(DamageType type) const; base::Vector2 GetWeaponPos(DamageType type) const;
@ -45,8 +47,6 @@ class Player {
DamageType GetWeaponType(const base::Vector2& pos); DamageType GetWeaponType(const base::Vector2& pos);
void SetBeamLength(DamageType type, float len);
void WarmupWeapon(DamageType type); void WarmupWeapon(DamageType type);
void CooldownWeapon(DamageType type); void CooldownWeapon(DamageType type);

View File

@ -15,6 +15,7 @@ SkyQuad::SkyQuad()
: shader_(Engine::Get().CreateRenderResource<Shader>()), : shader_(Engine::Get().CreateRenderResource<Shader>()),
sky_offset_{ sky_offset_{
0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().GetFloat())} { 0, Lerp(0.0f, 10.0f, Engine::Get().GetRandomGenerator().GetFloat())} {
last_sky_offset_ = sky_offset_;
} }
SkyQuad::~SkyQuad() = default; SkyQuad::~SkyQuad() = default;
@ -25,7 +26,8 @@ bool SkyQuad::Create() {
auto source = std::make_unique<ShaderSource>(); auto source = std::make_unique<ShaderSource>();
if (!source->Load("sky.glsl")) if (!source->Load("sky.glsl"))
return false; 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(); scale_ = engine.GetScreenSize();
@ -37,8 +39,8 @@ bool SkyQuad::Create() {
} }
void SkyQuad::Update(float delta_time) { void SkyQuad::Update(float delta_time) {
sky_offset_ += {0, delta_time * 0.04f}; last_sky_offset_ = sky_offset_;
color_animator_.Update(delta_time); sky_offset_ += {0, delta_time * speed_};
} }
void SkyQuad::Draw(float frame_frac) { void SkyQuad::Draw(float frame_frac) {
@ -46,13 +48,13 @@ void SkyQuad::Draw(float frame_frac) {
shader_->Activate(); shader_->Activate();
shader_->SetUniform("scale", scale_); shader_->SetUniform("scale", scale_);
shader_->SetUniform("projection", Engine::Get().GetProjectionMarix()); shader_->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader_->SetUniform("sky_offset", sky_offset); shader_->SetUniform("sky_offset", sky_offset);
shader_->SetUniform("nebula_color", shader_->SetUniform("nebula_color",
{nebula_color_.x, nebula_color_.y, nebula_color_.z}); {nebula_color_.x, nebula_color_.y, nebula_color_.z});
shader_->UploadUniforms();
Engine::Get().GetQuad()->Draw(); Engine::Get().GetQuad()->Draw();
last_sky_offset_ = sky_offset_;
} }
void SkyQuad::ContextLost() { void SkyQuad::ContextLost() {
@ -64,3 +66,7 @@ void SkyQuad::SwitchColor(const Vector4& color) {
std::bind(SmoothStep, std::placeholders::_1)); std::bind(SmoothStep, std::placeholders::_1));
color_animator_.Play(Animator::kBlending, false); color_animator_.Play(Animator::kBlending, false);
} }
void SkyQuad::SetSpeed(float speed) {
speed_ = speed;
}

View File

@ -40,6 +40,8 @@ class SkyQuad : public eng::Animatable {
void SwitchColor(const base::Vector4& color); void SwitchColor(const base::Vector4& color);
void SetSpeed(float speed);
private: private:
std::unique_ptr<eng::Shader> shader_; std::unique_ptr<eng::Shader> shader_;
@ -49,6 +51,8 @@ class SkyQuad : public eng::Animatable {
base::Vector2 scale_ = {1, 1}; base::Vector2 scale_ = {1, 1};
eng::Animator color_animator_; eng::Animator color_animator_;
float speed_ = 0;
}; };
#endif // SKY_QUAD_H #endif // SKY_QUAD_H

View File

@ -27,6 +27,8 @@ source_set("engine") {
"input_event.h", "input_event.h",
"mesh.cc", "mesh.cc",
"mesh.h", "mesh.h",
"persistent_data.cc",
"persistent_data.h",
"platform/asset_file.cc", "platform/asset_file.cc",
"platform/asset_file.h", "platform/asset_file.h",
"platform/platform_base.cc", "platform/platform_base.cc",
@ -35,14 +37,15 @@ source_set("engine") {
"platform/platform.h", "platform/platform.h",
"renderer/geometry.cc", "renderer/geometry.cc",
"renderer/geometry.h", "renderer/geometry.h",
"renderer/opengl.h", "renderer/opengl/opengl.h",
"renderer/render_command.cc", "renderer/opengl/render_command.cc",
"renderer/render_command.h", "renderer/opengl/render_command.h",
"renderer/opengl/renderer_opengl.cc",
"renderer/opengl/renderer_opengl.h",
"renderer/render_resource.cc", "renderer/render_resource.cc",
"renderer/render_resource.h", "renderer/render_resource.h",
"renderer/renderer_types.cc", "renderer/renderer_types.cc",
"renderer/renderer_types.h", "renderer/renderer_types.h",
"renderer/renderer.cc",
"renderer/renderer.h", "renderer/renderer.h",
"renderer/shader.cc", "renderer/shader.cc",
"renderer/shader.h", "renderer/shader.h",
@ -68,7 +71,7 @@ source_set("engine") {
"platform/asset_file_linux.cc", "platform/asset_file_linux.cc",
"platform/platform_linux.cc", "platform/platform_linux.cc",
"platform/platform_linux.h", "platform/platform_linux.h",
"renderer/renderer_linux.cc", "renderer/opengl/renderer_opengl_linux.cc",
] ]
#ldflags += ["-L/usr/X11R6/lib"] #ldflags += ["-L/usr/X11R6/lib"]

View File

@ -6,16 +6,16 @@ using namespace base;
namespace eng { namespace eng {
void Animatable::Translate(const Vector2& offset) { void Animatable::Translate(const Vector2& pos) {
offset_ += offset; position_ += pos;
} }
void Animatable::Scale(const Vector2& scale) { void Animatable::Scale(const Vector2& scale) {
scale_ *= scale; scale_ = scale;
} }
void Animatable::Scale(float scale) { void Animatable::Scale(float scale) {
scale_ *= scale; scale_ = {scale, scale};
} }
void Animatable::Rotate(float angle) { void Animatable::Rotate(float angle) {

View File

@ -11,20 +11,19 @@ class Animatable : public Drawable {
Animatable() = default; Animatable() = default;
~Animatable() override = default; ~Animatable() override = default;
void Translate(const base::Vector2& offset); void Translate(const base::Vector2& pos);
void Scale(const base::Vector2& scale); void Scale(const base::Vector2& scale);
void Scale(float scale); void Scale(float scale);
void Rotate(float angle); void Rotate(float angle);
void SetOffset(const base::Vector2& offset) { offset_ = offset; } void SetPosition(const base::Vector2& pos) { position_ = pos; }
void SetScale(const base::Vector2& scale) { scale_ = scale; } void SetSize(const base::Vector2& size) { size_ = size; }
void SetPivot(const base::Vector2& pivot) { pivot_ = pivot; }
void SetTheta(float theta); void SetTheta(float theta);
base::Vector2 GetOffset() const { return offset_; } base::Vector2 GetPosition() const { return position_; }
base::Vector2 GetScale() const { return scale_; } base::Vector2 GetSize() const { return size_ * scale_; }
base::Vector2 GetPivot() const { return pivot_; }
float GetTheta() const { return theta_; } float GetTheta() const { return theta_; }
base::Vector2 GetRotation() const { return rotation_; }
// Pure virtuals for frame animation support. // Pure virtuals for frame animation support.
virtual void SetFrame(size_t frame) = 0; virtual void SetFrame(size_t frame) = 0;
@ -35,25 +34,25 @@ class Animatable : public Drawable {
virtual base::Vector4 GetColor() const = 0; virtual base::Vector4 GetColor() const = 0;
void PlaceToLeftOf(const Animatable& s) { 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) { 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) { 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) { 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: protected:
base::Vector2 offset_ = {0, 0}; base::Vector2 position_ = {0, 0};
base::Vector2 size_ = {1, 1};
base::Vector2 scale_ = {1, 1}; base::Vector2 scale_ = {1, 1};
base::Vector2 pivot_ = {0, 0};
base::Vector2 rotation_ = {0, 1}; base::Vector2 rotation_ = {0, 1};
float theta_ = 0; float theta_ = 0;
}; };

View File

@ -3,11 +3,20 @@
#include "../base/interpolation.h" #include "../base/interpolation.h"
#include "../base/log.h" #include "../base/log.h"
#include "animatable.h" #include "animatable.h"
#include "engine.h"
using namespace base; using namespace base;
namespace eng { namespace eng {
Animator::Animator() {
Engine::Get().AddAnimator(this);
}
Animator::~Animator() {
Engine::Get().RemoveAnimator(this);
}
void Animator::Attach(Animatable* animatable) { void Animator::Attach(Animatable* animatable) {
elements_.push_back({animatable, elements_.push_back({animatable,
{0, 0}, {0, 0},
@ -18,7 +27,10 @@ void Animator::Attach(Animatable* animatable) {
void Animator::Play(int animation, bool loop) { void Animator::Play(int animation, bool loop) {
play_flags_ |= animation; play_flags_ |= animation;
loop_flags_ |= loop ? animation : 0; if (loop)
loop_flags_ |= animation;
else
loop_flags_ &= ~animation;
} }
void Animator::Pause(int animation) { void Animator::Pause(int animation) {
@ -43,6 +55,16 @@ void Animator::Stop(int animation) {
loop_flags_ &= ~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) { float Animator::GetTime(int animation) {
if ((animation & kMovement) != 0) if ((animation & kMovement) != 0)
return movement_time_; return movement_time_;
@ -55,7 +77,7 @@ float Animator::GetTime(int animation) {
return timer_time_; 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); DCHECK(time >= 0 && time <= 1);
if ((animation & kMovement) != 0) if ((animation & kMovement) != 0)
@ -68,6 +90,13 @@ void Animator::SetTime(int animation, float time) {
frame_time_ = time; frame_time_ = time;
if ((animation & kTimer) != 0) if ((animation & kTimer) != 0)
timer_time_ = time; 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) { void Animator::SetEndCallback(int animation, base::Closure cb) {
@ -95,7 +124,7 @@ void Animator::SetMovement(Vector2 direction,
movement_interpolator_ = std::move(interpolator); movement_interpolator_ = std::move(interpolator);
for (auto& a : elements_) for (auto& a : elements_)
a.movement_last_offset = {0, 0}; a.movement_last_pos = {0, 0};
} }
void Animator::SetRotation(float trget, void Animator::SetRotation(float trget,
@ -149,39 +178,41 @@ void Animator::Update(float delta_time) {
UpdateFrame(delta_time); UpdateFrame(delta_time);
if (play_flags_ & kTimer) if (play_flags_ & kTimer)
UpdateTimer(delta_time); UpdateTimer(delta_time);
}
void Animator::EvalAnim(float frame_time) {
for (auto& a : elements_) { for (auto& a : elements_) {
if (play_flags_ & kMovement) { if (play_flags_ & kMovement) {
float interpolated_time = movement_interpolator_ float time = movement_time_ + movement_speed_ * frame_time;
? movement_interpolator_(movement_time_) float interpolated_time =
: movement_time_; movement_interpolator_ ? movement_interpolator_(time) : time;
Vector2 offset = Vector2 pos = base::Lerp({0, 0}, movement_direction_, interpolated_time);
base::Lerp({0, 0}, movement_direction_, interpolated_time); a.animatable->Translate(pos - a.movement_last_pos);
a.animatable->Translate(offset - a.movement_last_offset); a.movement_last_pos = pos;
a.movement_last_offset = offset;
} }
if (play_flags_ & kRotation) { if (play_flags_ & kRotation) {
float interpolated_time = rotation_interpolator_ float time = rotation_time_ + rotation_speed_ * frame_time;
? rotation_interpolator_(rotation_time_) float interpolated_time =
: rotation_time_; rotation_interpolator_ ? rotation_interpolator_(time) : time;
float theta = base::Lerp(0.0f, rotation_target_, interpolated_time); float theta = base::Lerp(0.0f, rotation_target_, interpolated_time);
a.animatable->Rotate(theta - a.rotation_last_theta); a.animatable->Rotate(theta - a.rotation_last_theta);
a.rotation_last_theta = theta; a.rotation_last_theta = theta;
} }
if (play_flags_ & kBlending) { if (play_flags_ & kBlending) {
float interpolated_time = blending_interpolator_ float time = blending_time_ + blending_speed_ * frame_time;
? blending_interpolator_(blending_time_) float interpolated_time =
: blending_time_; blending_interpolator_ ? blending_interpolator_(time) : time;
Vector4 r = Vector4 r =
base::Lerp(a.blending_start, blending_target_, interpolated_time); base::Lerp(a.blending_start, blending_target_, interpolated_time);
a.animatable->SetColor(r); a.animatable->SetColor(r);
} }
if (play_flags_ & kFrames) { if (play_flags_ & kFrames) {
float time = frame_time_ + frame_speed_ * frame_time;
float interpolated_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 target = a.frame_start_ + frame_count_;
int r = base::Lerp(a.frame_start_, target, interpolated_time); int r = base::Lerp(a.frame_start_, target, interpolated_time);
if (r < target) if (r < target)

View File

@ -25,8 +25,8 @@ class Animator {
using Interpolator = std::function<float(float)>; using Interpolator = std::function<float(float)>;
Animator() = default; Animator();
~Animator() = default; ~Animator();
void Attach(Animatable* animatable); void Attach(Animatable* animatable);
@ -34,9 +34,11 @@ class Animator {
void Pause(int animation); void Pause(int animation);
void Stop(int animation); void Stop(int animation);
void PauseOrResumeAll(bool pause);
// Get/set current time of the given animation. // Get/set current time of the given animation.
float GetTime(int 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. // Set callback ro be called once animation ends.
void SetEndCallback(int animation, base::Closure cb); void SetEndCallback(int animation, base::Closure cb);
@ -67,13 +69,14 @@ class Animator {
void SetVisible(bool visible); void SetVisible(bool visible);
void Update(float delta_time); void Update(float delta_time);
void EvalAnim(float frame_time);
bool IsPlaying(int animation) const { return play_flags_ & animation; } bool IsPlaying(int animation) const { return play_flags_ & animation; }
private: private:
struct Element { struct Element {
Animatable* animatable; Animatable* animatable;
base::Vector2 movement_last_offset = {0, 0}; base::Vector2 movement_last_pos = {0, 0};
float rotation_last_theta = 0; float rotation_last_theta = 0;
base::Vector4 blending_start = {0, 0, 0, 0}; base::Vector4 blending_start = {0, 0, 0, 0};
int frame_start_ = 0; int frame_start_ = 0;
@ -81,6 +84,7 @@ class Animator {
unsigned int play_flags_ = 0; unsigned int play_flags_ = 0;
unsigned int loop_flags_ = 0; unsigned int loop_flags_ = 0;
unsigned int resume_flags_ = 0;
std::vector<Element> elements_; std::vector<Element> elements_;
base::Vector2 movement_direction_ = {0, 0}; base::Vector2 movement_direction_ = {0, 0};

View File

@ -157,11 +157,11 @@ void AudioAlsa::Resume() {
suspend_audio_thread_.store(false, std::memory_order_relaxed); suspend_audio_thread_.store(false, std::memory_order_relaxed);
} }
size_t AudioAlsa::GetSampleRate() { int AudioAlsa::GetHardwareSampleRate() {
return sample_rate_; return sample_rate_;
} }
bool AudioAlsa::StartAudioThread() { void AudioAlsa::StartAudioThread() {
LOG << "Starting audio thread."; LOG << "Starting audio thread.";
DCHECK(!terminate_audio_thread_.load(std::memory_order_relaxed)); 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)) { while (suspend_audio_thread_.load(std::memory_order_relaxed)) {
if (terminate_audio_thread_.load(std::memory_order_relaxed)) if (terminate_audio_thread_.load(std::memory_order_relaxed))
return; return;
std::this_thread::yield(); // Avoid busy-looping.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
RenderAudio(buffer.get(), num_frames); RenderAudio(buffer.get(), num_frames);

View File

@ -24,21 +24,21 @@ class AudioAlsa : public AudioBase {
void Suspend(); void Suspend();
void Resume(); void Resume();
size_t GetSampleRate(); int GetHardwareSampleRate();
private: private:
// Handle for the PCM device. // Handle for the PCM device.
snd_pcm_t* device_; snd_pcm_t* device_;
std::thread audio_thread_; std::thread audio_thread_;
std::atomic<bool> terminate_audio_thread_ = false; std::atomic<bool> terminate_audio_thread_{false};
std::atomic<bool> suspend_audio_thread_ = false; std::atomic<bool> suspend_audio_thread_{false};
size_t num_channels_ = 0; size_t num_channels_ = 0;
size_t sample_rate_ = 0; int sample_rate_ = 0;
size_t period_size_ = 0; size_t period_size_ = 0;
bool StartAudioThread(); void StartAudioThread();
void TerminateAudioThread(); void TerminateAudioThread();
void AudioThreadMain(); void AudioThreadMain();

View File

@ -18,16 +18,18 @@ AudioBase::~AudioBase() = default;
void AudioBase::Play(std::shared_ptr<AudioSample> sample) { void AudioBase::Play(std::shared_ptr<AudioSample> sample) {
if (audio_enabled_) { if (audio_enabled_) {
std::lock_guard<Spinlock> scoped_lock(lock_); std::lock_guard<std::mutex> scoped_lock(lock_);
samples_[0].push_back(sample); samples_[0].push_back(sample);
} else { } else if ((sample->flags.load(std::memory_order_relaxed) &
AudioSample::kStopped) == 0) {
sample->active = false; sample->active = false;
} }
} }
void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) { void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
{ {
std::lock_guard<Spinlock> scoped_lock(lock_); std::unique_lock<std::mutex> scoped_lock(lock_, std::try_to_lock);
if (scoped_lock)
samples_[1].splice(samples_[1].end(), samples_[0]); samples_[1].splice(samples_[1].end(), samples_[0]);
} }
@ -58,7 +60,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
size_t channel_offset = size_t channel_offset =
(flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound() (flags & AudioSample::kSimulateStereo) && !sound->is_streaming_sound()
? sound->hz() / 10 ? sound->sample_rate() / 10
: 0; : 0;
DCHECK(num_samples || sound->is_streaming_sound()); 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. // Basic resampling for variations.
accumulator += step; accumulator += step;
src_index += accumulator / 10; src_index += accumulator / 100;
accumulator %= 10; accumulator %= 100;
} }
// Advance source index. // Advance source index.
@ -122,7 +124,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
src[1] = src[0]; // mono. src[1] = src[0]; // mono.
num_samples = sound->GetNumSamples(); num_samples = sound->GetNumSamples();
Worker::Get().EnqueueTask(HERE, Worker::Get().PostTask(HERE,
std::bind(&AudioBase::DoStream, this, *it, std::bind(&AudioBase::DoStream, this, *it,
flags & AudioSample::kLoop)); flags & AudioSample::kLoop));
} else if (num_samples) { } else if (num_samples) {
@ -141,7 +143,7 @@ void AudioBase::RenderAudio(float* output_buffer, size_t num_frames) {
(!sound->is_streaming_sound() || (!sound->is_streaming_sound() ||
!sample->streaming_in_progress.load(std::memory_order_relaxed))) { !sample->streaming_in_progress.load(std::memory_order_relaxed))) {
sample->marked_for_removal = false; sample->marked_for_removal = false;
main_thread_task_runner_->EnqueueTask( main_thread_task_runner_->PostTask(
HERE, std::bind(&AudioBase::EndCallback, this, *it)); HERE, std::bind(&AudioBase::EndCallback, this, *it));
it = samples_[1].erase(it); it = samples_[1].erase(it);
} else { } else {
@ -159,12 +161,10 @@ void AudioBase::DoStream(std::shared_ptr<AudioSample> sample, bool loop) {
} }
void AudioBase::EndCallback(std::shared_ptr<AudioSample> sample) { void AudioBase::EndCallback(std::shared_ptr<AudioSample> sample) {
AudioSample* s = sample.get(); sample->active = false;
s->active = false; if (sample->end_cb)
sample->end_cb();
if (s->end_cb)
s->end_cb();
} }
} // namespace eng } // namespace eng

View File

@ -3,9 +3,9 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex>
#include "../../base/closure.h" #include "../../base/closure.h"
#include "../../base/spinlock.h"
#include "audio_sample.h" #include "audio_sample.h"
namespace base { namespace base {
@ -32,7 +32,7 @@ class AudioBase {
private: private:
std::list<std::shared_ptr<AudioSample>> samples_[2]; std::list<std::shared_ptr<AudioSample>> samples_[2];
base::Spinlock lock_; std::mutex lock_;
base::TaskRunner* main_thread_task_runner_; base::TaskRunner* main_thread_task_runner_;

View File

@ -25,14 +25,14 @@ void AudioOboe::Shutdown() {
} }
void AudioOboe::Suspend() { void AudioOboe::Suspend() {
stream_->pause(); stream_->stop();
} }
void AudioOboe::Resume() { void AudioOboe::Resume() {
stream_->start(); RestartStream();
} }
size_t AudioOboe::GetSampleRate() { int AudioOboe::GetHardwareSampleRate() {
return stream_->getSampleRate(); return stream_->getSampleRate();
} }

View File

@ -23,7 +23,7 @@ class AudioOboe : public AudioBase {
void Suspend(); void Suspend();
void Resume(); void Resume();
size_t GetSampleRate(); int GetHardwareSampleRate();
private: private:
class StreamCallback : public oboe::AudioStreamCallback { class StreamCallback : public oboe::AudioStreamCallback {

View File

@ -61,7 +61,7 @@ void AudioResource::SetLoop(bool loop) {
if (loop) if (loop)
sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed); sample_->flags.fetch_or(AudioSample::kLoop, std::memory_order_relaxed);
else 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) { void AudioResource::SetSimulateStereo(bool simulate) {
@ -69,12 +69,12 @@ void AudioResource::SetSimulateStereo(bool simulate) {
sample_->flags.fetch_or(AudioSample::kSimulateStereo, sample_->flags.fetch_or(AudioSample::kSimulateStereo,
std::memory_order_relaxed); std::memory_order_relaxed);
else else
sample_->flags.fetch_and(AudioSample::kSimulateStereo, sample_->flags.fetch_and(~AudioSample::kSimulateStereo,
std::memory_order_relaxed); std::memory_order_relaxed);
} }
void AudioResource::SetResampleStep(size_t step) { 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) { void AudioResource::SetMaxAmplitude(float max_amplitude) {

View File

@ -28,7 +28,7 @@ struct AudioSample {
// Write accessed by main thread, read-only accessed by audio thread. // Write accessed by main thread, read-only accessed by audio thread.
std::atomic<unsigned> flags{0}; std::atomic<unsigned> flags{0};
std::atomic<size_t> step{10}; std::atomic<size_t> step{100};
std::atomic<float> amplitude_inc{0}; std::atomic<float> amplitude_inc{0};
std::atomic<float> max_amplitude{1.0f}; std::atomic<float> max_amplitude{1.0f};

View File

@ -15,7 +15,6 @@
#include "mesh.h" #include "mesh.h"
#include "platform/platform.h" #include "platform/platform.h"
#include "renderer/geometry.h" #include "renderer/geometry.h"
#include "renderer/render_command.h"
#include "renderer/renderer.h" #include "renderer/renderer.h"
#include "renderer/shader.h" #include "renderer/shader.h"
#include "renderer/texture.h" #include "renderer/texture.h"
@ -68,6 +67,8 @@ bool Engine::Initialize() {
projection_ = base::Ortho(-1.0, 1.0, -aspect_ratio, aspect_ratio); projection_ = base::Ortho(-1.0, 1.0, -aspect_ratio, aspect_ratio);
} }
LOG << "image scale factor: " << GetImageScaleFactor();
if (renderer_->SupportsDXT5()) { if (renderer_->SupportsDXT5()) {
tex_comp_alpha_ = TextureCompressor::Create(TextureCompressor::kFormatDXT5); tex_comp_alpha_ = TextureCompressor::Create(TextureCompressor::kFormatDXT5);
} else if (renderer_->SupportsATC()) { } else if (renderer_->SupportsATC()) {
@ -95,7 +96,7 @@ bool Engine::Initialize() {
game_ = GameFactoryBase::CreateGame(""); game_ = GameFactoryBase::CreateGame("");
if (!game_) { if (!game_) {
printf("No game found to run.\n"); LOG << "No game found to run.";
return false; return false;
} }
@ -113,6 +114,10 @@ void Engine::Shutdown() {
void Engine::Update(float delta_time) { void Engine::Update(float delta_time) {
seconds_accumulated_ += delta_time; seconds_accumulated_ += delta_time;
++tick_;
for (auto d : animators_)
d->Update(delta_time);
game_->Update(delta_time); game_->Update(delta_time);
@ -135,15 +140,20 @@ void Engine::Update(float delta_time) {
} }
void Engine::Draw(float frame_frac) { void Engine::Draw(float frame_frac) {
for (auto d : animators_)
d->EvalAnim(time_step_ * frame_frac);
drawables_.sort( drawables_.sort(
[](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); }); [](auto& a, auto& b) { return a->GetZOrder() < b->GetZOrder(); });
renderer_->PrepareForDrawing();
for (auto d : drawables_) { for (auto d : drawables_) {
if (d->IsVisible()) if (d->IsVisible())
d->Draw(frame_frac); d->Draw(frame_frac);
} }
renderer_->EnqueueCommand(std::make_unique<CmdPresent>()); renderer_->Present();
} }
void Engine::LostFocus() { void Engine::LostFocus() {
@ -153,11 +163,11 @@ void Engine::LostFocus() {
game_->LostFocus(); game_->LostFocus();
} }
void Engine::GainedFocus() { void Engine::GainedFocus(bool from_interstitial_ad) {
audio_->Resume(); audio_->Resume();
if (game_) if (game_)
game_->GainedFocus(); game_->GainedFocus(from_interstitial_ad);
} }
void Engine::AddDrawable(Drawable* drawable) { 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() { void Engine::Exit() {
platform_->Exit(); platform_->Exit();
} }
@ -190,27 +214,16 @@ Vector2 Engine::ToPosition(const Vector2& vec) {
void Engine::SetImageSource(const std::string& asset_name, void Engine::SetImageSource(const std::string& asset_name,
const std::string& file_name, const std::string& file_name,
bool persistent) { bool persistent) {
std::shared_ptr<Texture> texture; SetImageSource(
auto it = textures_.find(asset_name); asset_name,
if (it != textures_.end()) { [file_name]() -> std::unique_ptr<Image> {
texture = it->second.texture;
it->second.asset_file = file_name;
it->second.create_image = nullptr;
it->second.persistent = persistent;
} else {
texture = CreateRenderResource<Texture>();
textures_[asset_name] = {texture, file_name, nullptr, persistent};
}
if (persistent) {
auto image = std::make_unique<Image>(); auto image = std::make_unique<Image>();
if (image->Load(file_name)) { if (!image->Load(file_name))
return nullptr;
image->Compress(); image->Compress();
texture->Update(std::move(image)); return image;
} else { },
texture->Destroy(); persistent);
}
}
} }
void Engine::SetImageSource(const std::string& asset_name, void Engine::SetImageSource(const std::string& asset_name,
@ -221,11 +234,10 @@ void Engine::SetImageSource(const std::string& asset_name,
if (it != textures_.end()) { if (it != textures_.end()) {
texture = it->second.texture; texture = it->second.texture;
it->second.create_image = create_image; it->second.create_image = create_image;
it->second.asset_file.clear();
it->second.persistent = persistent; it->second.persistent = persistent;
} else { } else {
texture = CreateRenderResource<Texture>(); texture = CreateRenderResource<Texture>();
textures_[asset_name] = {texture, "", create_image, persistent}; textures_[asset_name] = {texture, create_image, persistent};
} }
if (persistent) { if (persistent) {
@ -242,17 +254,7 @@ void Engine::RefreshImage(const std::string& asset_name) {
if (it == textures_.end()) if (it == textures_.end())
return; return;
std::unique_ptr<Image> image; auto image = it->second.create_image();
if (!it->second.asset_file.empty()) {
image = std::make_unique<Image>();
if (image->Load(it->second.asset_file))
image->Compress();
else
image.reset();
} else if (it->second.create_image) {
image = it->second.create_image();
}
if (image) if (image)
it->second.texture->Update(std::move(image)); it->second.texture->Update(std::move(image));
else else
@ -278,9 +280,12 @@ std::unique_ptr<AudioResource> Engine::CreateAudioResource() {
} }
void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) { void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
if (replaying_)
return;
switch (event->GetType()) { switch (event->GetType()) {
case InputEvent::kDragEnd: case InputEvent::kDragEnd:
if (((GetScreenSize() / 2) * 0.9f - event->GetVector(0)).Magnitude() <= if (((GetScreenSize() / 2) * 0.9f - event->GetVector()).Magnitude() <=
0.25f) { 0.25f) {
SetSatsVisible(!stats_->IsVisible()); SetSatsVisible(!stats_->IsVisible());
// TODO: Enqueue DragCancel so we can consume this event. // TODO: Enqueue DragCancel so we can consume this event.
@ -295,9 +300,9 @@ void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
break; break;
case InputEvent::kDrag: case InputEvent::kDrag:
if (stats_->IsVisible()) { if (stats_->IsVisible()) {
if ((stats_->GetOffset() - event->GetVector(0)).Magnitude() <= if ((stats_->GetPosition() - event->GetVector()).Magnitude() <=
stats_->GetScale().y) stats_->GetSize().y)
stats_->SetOffset(event->GetVector(0)); stats_->SetPosition(event->GetVector());
// TODO: Enqueue DragCancel so we can consume this event. // TODO: Enqueue DragCancel so we can consume this event.
} }
break; break;
@ -310,18 +315,91 @@ void Engine::AddInputEvent(std::unique_ptr<InputEvent> event) {
std::unique_ptr<InputEvent> Engine::GetNextInputEvent() { std::unique_ptr<InputEvent> Engine::GetNextInputEvent() {
std::unique_ptr<InputEvent> event; std::unique_ptr<InputEvent> 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>(
(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()) { if (!input_queue_.empty()) {
event.swap(input_queue_.front()); event.swap(input_queue_.front());
input_queue_.pop_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; 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) { void Engine::Vibrate(int duration) {
if (vibration_enabled_) if (vibration_enabled_)
platform_->Vibrate(duration); 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) { void Engine::SetEnableAudio(bool enable) {
audio_->SetEnableAudio(enable); audio_->SetEnableAudio(enable);
} }
@ -342,12 +420,26 @@ int Engine::GetDeviceDpi() const {
return platform_->GetDeviceDpi(); return platform_->GetDeviceDpi();
} }
float Engine::GetImageScaleFactor() const {
float width_inch = static_cast<float>(renderer_->screen_width()) /
static_cast<float>(platform_->GetDeviceDpi());
return 2.57143f / width_inch;
}
const std::string& Engine::GetRootPath() const { const std::string& Engine::GetRootPath() const {
return platform_->GetRootPath(); return platform_->GetRootPath();
} }
size_t Engine::GetAudioSampleRate() { const std::string& Engine::GetDataPath() const {
return audio_->GetSampleRate(); return platform_->GetDataPath();
}
const std::string& Engine::GetSharedDataPath() const {
return platform_->GetSharedDataPath();
}
int Engine::GetAudioHardwareSampleRate() {
return audio_->GetHardwareSampleRate();
} }
bool Engine::IsMobile() const { bool Engine::IsMobile() const {
@ -369,12 +461,16 @@ void Engine::ContextLost() {
} }
bool Engine::CreateRenderResources() { 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. // Create the quad geometry we can reuse for all sprites.
auto quad_mesh = std::make_unique<Mesh>(); auto quad_mesh = std::make_unique<Mesh>();
if (!quad_mesh->Load("engine/quad.mesh")) { quad_mesh->Create(kPrimitive_TriangleStrip, vertex_description, 4, vertices);
LOG << "Could not create quad mesh.";
return false;
}
quad_->Create(std::move(quad_mesh)); quad_->Create(std::move(quad_mesh));
// Create the shader we can reuse for texture rendering. // Create the shader we can reuse for texture rendering.
@ -383,7 +479,8 @@ bool Engine::CreateRenderResources() {
LOG << "Could not create pass through shader."; LOG << "Could not create pass through shader.";
return false; 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. // Create the shader we can reuse for solid rendering.
source = std::make_unique<ShaderSource>(); source = std::make_unique<ShaderSource>();
@ -391,7 +488,8 @@ bool Engine::CreateRenderResources() {
LOG << "Could not create solid shader."; LOG << "Could not create solid shader.";
return false; return false;
} }
solid_shader_->Create(std::move(source), quad_->vertex_description()); solid_shader_->Create(std::move(source), quad_->vertex_description(),
quad_->primitive());
return true; return true;
} }
@ -414,10 +512,10 @@ std::unique_ptr<Image> Engine::PrintStats() {
line = "fps: "; line = "fps: ";
line += std::to_string(fps_); line += std::to_string(fps_);
lines.push_back(line); lines.push_back(line);
line = "cmd: "; // line = "cmd: ";
line += std::to_string(renderer_->global_queue_size() + // line += std::to_string(renderer_->global_queue_size() +
renderer_->render_queue_size()); // renderer_->render_queue_size());
lines.push_back(line); // lines.push_back(line);
constexpr int margin = 5; constexpr int margin = 5;
int line_height = system_font_->GetLineHeight(); int line_height = system_font_->GetLineHeight();

View File

@ -10,6 +10,7 @@
#include "../base/random.h" #include "../base/random.h"
#include "../base/vecmath.h" #include "../base/vecmath.h"
#include "audio/audio_forward.h" #include "audio/audio_forward.h"
#include "persistent_data.h"
#include "platform/platform_forward.h" #include "platform/platform_forward.h"
#include "renderer/render_resource.h" #include "renderer/render_resource.h"
@ -17,6 +18,7 @@ class TextureCompressor;
namespace eng { namespace eng {
class Animator;
class AudioResource; class AudioResource;
class Font; class Font;
class Game; class Game;
@ -46,11 +48,14 @@ class Engine {
void Draw(float frame_frac); void Draw(float frame_frac);
void LostFocus(); void LostFocus();
void GainedFocus(); void GainedFocus(bool from_interstitial_ad);
void AddDrawable(Drawable* drawable); void AddDrawable(Drawable* drawable);
void RemoveDrawable(Drawable* drawable); void RemoveDrawable(Drawable* drawable);
void AddAnimator(Animator* animator);
void RemoveAnimator(Animator* animator);
void Exit(); void Exit();
// Convert size from pixels to viewport scale. // Convert size from pixels to viewport scale.
@ -83,9 +88,20 @@ class Engine {
void AddInputEvent(std::unique_ptr<InputEvent> event); void AddInputEvent(std::unique_ptr<InputEvent> event);
std::unique_ptr<InputEvent> GetNextInputEvent(); std::unique_ptr<InputEvent> 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. // Vibrate (if supported by the platform) for the specified duration.
void Vibrate(int 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 SetImageDpi(float dpi) { image_dpi_ = dpi; }
void SetEnableAudio(bool enable); void SetEnableAudio(bool enable);
@ -112,13 +128,19 @@ class Engine {
// Return screen size in viewport scale. // Return screen size in viewport scale.
base::Vector2 GetScreenSize() const { return screen_size_; } base::Vector2 GetScreenSize() const { return screen_size_; }
const base::Matrix4x4& GetProjectionMarix() const { return projection_; } const base::Matrix4x4& GetProjectionMatrix() const { return projection_; }
int GetDeviceDpi() const; int GetDeviceDpi() const;
float GetImageScaleFactor() const;
const std::string& GetRootPath() const; const std::string& GetRootPath() const;
size_t GetAudioSampleRate(); const std::string& GetDataPath() const;
const std::string& GetSharedDataPath() const;
int GetAudioHardwareSampleRate();
bool IsMobile() const; bool IsMobile() const;
@ -126,13 +148,15 @@ class Engine {
float image_dpi() const { return image_dpi_; } float image_dpi() const { return image_dpi_; }
float time_step() { return time_step_; }
int fps() const { return fps_; }
private: private:
// Class holding information about texture resources managed by engine. // 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 { struct TextureResource {
std::shared_ptr<Texture> texture; std::shared_ptr<Texture> texture;
std::string asset_file;
CreateImageCB create_image; CreateImageCB create_image;
bool persistent = false; bool persistent = false;
}; };
@ -143,7 +167,7 @@ class Engine {
Renderer* renderer_ = nullptr; Renderer* renderer_ = nullptr;
Audio* audio_; Audio* audio_ = nullptr;
std::unique_ptr<Game> game_; std::unique_ptr<Game> game_;
@ -161,6 +185,8 @@ class Engine {
std::list<Drawable*> drawables_; std::list<Drawable*> drawables_;
std::list<Animator*> animators_;
// Textures mapped by asset name. // Textures mapped by asset name.
std::unordered_map<std::string, TextureResource> textures_; std::unordered_map<std::string, TextureResource> textures_;
@ -171,12 +197,20 @@ class Engine {
float seconds_accumulated_ = 0.0f; float seconds_accumulated_ = 0.0f;
float time_step_ = 1.0f / 60.0f;
size_t tick_ = 0;
float image_dpi_ = 200; float image_dpi_ = 200;
bool vibration_enabled_ = true; bool vibration_enabled_ = true;
std::deque<std::unique_ptr<InputEvent>> input_queue_; std::deque<std::unique_ptr<InputEvent>> input_queue_;
PersistentData replay_data_;
bool recording_ = false;
bool replaying_ = false;
int replay_index_ = 0;
base::Random random_; base::Random random_;
std::unique_ptr<RenderResource> CreateRenderResourceInternal( std::unique_ptr<RenderResource> CreateRenderResourceInternal(

View File

@ -16,7 +16,7 @@ class Game {
virtual void LostFocus() = 0; virtual void LostFocus() = 0;
virtual void GainedFocus() = 0; virtual void GainedFocus(bool from_interstitial_ad) = 0;
private: private:
Game(const Game&) = delete; Game(const Game&) = delete;

View File

@ -151,6 +151,8 @@ bool Image::Load(const std::string& file_name) {
return false; return false;
} }
LOG << "Loaded " << file_name << ". number of color components: " << c;
uint8_t* converted_buffer = NULL; uint8_t* converted_buffer = NULL;
switch (c) { switch (c) {
case 1: case 1:

View File

@ -33,8 +33,16 @@ void ImageQuad::Destory() {
void ImageQuad::AutoScale() { void ImageQuad::AutoScale() {
auto& engine = Engine::Get(); auto& engine = Engine::Get();
Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()}; Vector2 dimensions = {GetFrameWidth(), GetFrameHeight()};
SetScale(engine.ToScale(dimensions)); Vector2 size = engine.ToScale(dimensions);
Scale((float)engine.GetDeviceDpi() / engine.image_dpi()); float s =
static_cast<float>(engine.image_dpi()) * engine.GetImageScaleFactor();
size *= static_cast<float>(engine.GetDeviceDpi()) / s;
SetSize(size);
}
void ImageQuad::SetCustomShader(std::shared_ptr<Shader> shader) {
custom_shader_ = shader;
custom_uniforms_.clear();
} }
void ImageQuad::SetFrame(size_t frame) { void ImageQuad::SetFrame(size_t frame) {
@ -58,18 +66,25 @@ void ImageQuad::Draw(float frame_frac) {
Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(), Vector2 tex_scale = {GetFrameWidth() / texture_->GetWidth(),
GetFrameHeight() / texture_->GetHeight()}; GetFrameHeight() / texture_->GetHeight()};
Shader* shader = Engine::Get().GetPassThroughShader(); Shader* shader = custom_shader_ ? custom_shader_.get()
: Engine::Get().GetPassThroughShader();
shader->Activate(); shader->Activate();
shader->SetUniform("offset", offset_); shader->SetUniform("offset", position_);
shader->SetUniform("scale", scale_); shader->SetUniform("scale", GetSize());
shader->SetUniform("pivot", pivot_);
shader->SetUniform("rotation", rotation_); shader->SetUniform("rotation", rotation_);
shader->SetUniform("tex_offset", GetUVOffset(current_frame_)); shader->SetUniform("tex_offset", GetUVOffset(current_frame_));
shader->SetUniform("tex_scale", tex_scale); shader->SetUniform("tex_scale", tex_scale);
shader->SetUniform("projection", Engine::Get().GetProjectionMarix()); shader->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader->SetUniform("color", color_); 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(); Engine::Get().GetQuad()->Draw();
} }

View File

@ -7,9 +7,12 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map>
#include <variant>
namespace eng { namespace eng {
class Shader;
class Texture; class Texture;
class ImageQuad : public Animatable { class ImageQuad : public Animatable {
@ -26,6 +29,13 @@ class ImageQuad : public Animatable {
void AutoScale(); void AutoScale();
void SetCustomShader(std::shared_ptr<Shader> shader);
template <typename T>
void SetCustomUniform(const std::string& name, T value) {
custom_uniforms_[name] = UniformValue(value);
}
// Animatable interface. // Animatable interface.
void SetFrame(size_t frame) override; void SetFrame(size_t frame) override;
size_t GetFrame() const override { return current_frame_; } size_t GetFrame() const override { return current_frame_; }
@ -37,8 +47,18 @@ class ImageQuad : public Animatable {
void Draw(float frame_frac) override; void Draw(float frame_frac) override;
private: private:
using UniformValue = std::variant<base::Vector2,
base::Vector3,
base::Vector4,
base::Matrix4x4,
float,
int>;
std::shared_ptr<Texture> texture_; std::shared_ptr<Texture> texture_;
std::shared_ptr<Shader> custom_shader_;
std::unordered_map<std::string, UniformValue> custom_uniforms_;
size_t current_frame_ = 0; size_t current_frame_ = 0;
std::array<int, 2> num_frames_ = {1, 1}; // horizontal, vertical std::array<int, 2> num_frames_ = {1, 1}; // horizontal, vertical
int frame_width_ = 0; int frame_width_ = 0;

View File

@ -31,10 +31,7 @@ class InputEvent {
size_t GetPointerId() const { return pointer_id_; } size_t GetPointerId() const { return pointer_id_; }
base::Vector2 GetVector(size_t i) const { base::Vector2 GetVector() const { return vec_; }
DCHECK(i < 2);
return vec_;
}
char GetKeyPress() const { return key_; } char GetKeyPress() const { return key_; }

View File

@ -9,10 +9,6 @@
namespace eng { 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, bool Mesh::Create(Primitive primitive,
const std::string& vertex_description, const std::string& vertex_description,
size_t num_vertices, size_t num_vertices,
@ -105,6 +101,8 @@ bool Mesh::Load(const std::string& file_name) {
return false; return false;
} }
LOG << "Loaded " << file_name << ". Vertex array size: " << vertices.size();
vertices_ = std::make_unique<char[]>(vertex_buffer_size); vertices_ = std::make_unique<char[]>(vertex_buffer_size);
char* dst = vertices_.get(); char* dst = vertices_.get();

View File

@ -9,8 +9,6 @@ namespace eng {
class Mesh { class Mesh {
public: public:
static const char kLayoutDelimiter[];
Mesh() = default; Mesh() = default;
~Mesh() = default; ~Mesh() = default;
@ -40,7 +38,7 @@ class Mesh {
bool IsValid() const { return !!vertices_.get(); } bool IsValid() const { return !!vertices_.get(); }
protected: private:
Primitive primitive_ = kPrimitive_TriangleStrip; Primitive primitive_ = kPrimitive_TriangleStrip;
VertexDescripton vertex_description_; VertexDescripton vertex_description_;
size_t num_vertices_ = 0; size_t num_vertices_ = 0;

View File

@ -0,0 +1,104 @@
#include "persistent_data.h"
#include <memory>
#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<char[]> 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<char[]>(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<Json::CharReader> 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<Json::StreamWriter> 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

View File

@ -0,0 +1,40 @@
#ifndef SAVE_GAME_H
#define SAVE_GAME_H
#include <string>
#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

View File

@ -1,6 +1,7 @@
#include "platform_android.h" #include "platform_android.h"
#include <android_native_app_glue.h> #include <android_native_app_glue.h>
#include <jni.h>
#include <unistd.h> #include <unistd.h>
#include <memory> #include <memory>
@ -17,6 +18,22 @@ using namespace base;
namespace { 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) { std::string GetApkPath(ANativeActivity* activity) {
JNIEnv* env = nullptr; JNIEnv* env = nullptr;
activity->vm->AttachCurrentThread(&env, nullptr); activity->vm->AttachCurrentThread(&env, nullptr);
@ -45,6 +62,107 @@ std::string GetApkPath(ANativeActivity* activity) {
return apk_path; 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) { void Vibrate(ANativeActivity* activity, int duration) {
JNIEnv* env = nullptr; JNIEnv* env = nullptr;
activity->vm->AttachCurrentThread(&env, nullptr); activity->vm->AttachCurrentThread(&env, nullptr);
@ -78,7 +196,7 @@ void Vibrate(ANativeActivity* activity, int duration) {
activity->vm->DetachCurrentThread(); activity->vm->DetachCurrentThread();
} }
int32_t getDensityDpi(android_app* app) { int32_t GetDensityDpi(android_app* app) {
AConfiguration* config = AConfiguration_new(); AConfiguration* config = AConfiguration_new();
AConfiguration_fromAssetManager(config, app->activity->assetManager); AConfiguration_fromAssetManager(config, app->activity->assetManager);
int32_t density = AConfiguration_getDensity(config); int32_t density = AConfiguration_getDensity(config);
@ -129,7 +247,7 @@ int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
switch (flags) { switch (flags) {
case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_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_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = true; platform->pointer_down_[pointer_id] = true;
input_event = input_event =
@ -139,7 +257,7 @@ int32_t PlatformAndroid::HandleInput(android_app* app, AInputEvent* event) {
case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_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_pos_[pointer_id] = pos[pointer_id];
platform->pointer_down_[pointer_id] = false; platform->pointer_down_[pointer_id] = false;
input_event = std::make_unique<InputEvent>( input_event = std::make_unique<InputEvent>(
@ -220,7 +338,8 @@ void PlatformAndroid::HandleCmd(android_app* app, int32_t cmd) {
platform->timer_.Reset(); platform->timer_.Reset();
platform->has_focus_ = true; platform->has_focus_ = true;
if (platform->engine_) if (platform->engine_)
platform->engine_->GainedFocus(); platform->engine_->GainedFocus(g_showing_interstitial_ad);
g_showing_interstitial_ad = false;
break; break;
case APP_CMD_LOST_FOCUS: case APP_CMD_LOST_FOCUS:
@ -243,10 +362,16 @@ void PlatformAndroid::Initialize(android_app* app) {
mobile_device_ = true; mobile_device_ = true;
root_path_ = GetApkPath(app->activity); root_path_ = ::GetApkPath(app->activity);
LOG << "Root path: " << root_path_.c_str(); 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_; LOG << "Device DPI: " << device_dpi_;
app->userData = reinterpret_cast<void*>(this); app->userData = reinterpret_cast<void*>(this);
@ -283,6 +408,18 @@ void PlatformAndroid::Vibrate(int duration) {
::Vibrate(app_->activity, 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 } // namespace eng
void android_main(android_app* app) { void android_main(android_app* app) {

View File

@ -22,6 +22,12 @@ class PlatformAndroid : public PlatformBase {
void Vibrate(int duration); void Vibrate(int duration);
void ShowInterstitialAd();
void ShareFile(const std::string& file_name);
void SetKeepScreenOn(bool keep_screen_on);
private: private:
android_app* app_ = nullptr; android_app* app_ = nullptr;

View File

@ -1,15 +1,10 @@
#include "platform.h" #include "platform.h"
#include <thread>
#include "../../base/log.h" #include "../../base/log.h"
#include "../../base/task_runner.h" #include "../../base/task_runner.h"
#include "../audio/audio.h" #include "../audio/audio.h"
#include "../engine.h" #include "../engine.h"
#include "../renderer/renderer.h" #include "../renderer/opengl/renderer_opengl.h"
// Save battery on mobile devices.
#define USE_SLEEP
using namespace base; using namespace base;
@ -33,7 +28,7 @@ void PlatformBase::Initialize() {
throw internal_error; throw internal_error;
} }
renderer_ = std::make_unique<Renderer>(); renderer_ = std::make_unique<RendererOpenGL>();
} }
void PlatformBase::Shutdown() { void PlatformBase::Shutdown() {
@ -52,11 +47,7 @@ void PlatformBase::RunMainLoop() {
} }
// Use fixed time steps. // Use fixed time steps.
constexpr float time_step = 1.0f / 60.0f; float time_step = engine_->time_step();
#ifdef USE_SLEEP
constexpr float epsilon = 0.0001f;
#endif // USE_SLEEP
timer_.Reset(); timer_.Reset();
float accumulator = 0.0; float accumulator = 0.0;
@ -66,20 +57,8 @@ void PlatformBase::RunMainLoop() {
engine_->Draw(frame_frac); engine_->Draw(frame_frac);
// Accumulate time. // Accumulate time.
#ifdef USE_SLEEP
while (accumulator < time_step) {
timer_.Update(); timer_.Update();
accumulator += timer_.GetSecondsPassed(); 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. // Subdivide the frame time.
while (accumulator >= time_step) { while (accumulator >= time_step) {

View File

@ -26,6 +26,10 @@ class PlatformBase {
const std::string& GetRootPath() const { return root_path_; } 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_; } bool mobile_device() const { return mobile_device_; }
static class InternalError : public std::exception { static class InternalError : public std::exception {
@ -35,8 +39,10 @@ class PlatformBase {
base::Timer timer_; base::Timer timer_;
bool mobile_device_ = false; bool mobile_device_ = false;
int device_dpi_ = 200; int device_dpi_ = 100;
std::string root_path_; std::string root_path_;
std::string data_path_;
std::string shared_data_path_;
bool has_focus_ = false; bool has_focus_ = false;
bool should_exit_ = false; bool should_exit_ = false;

View File

@ -1,8 +1,5 @@
#include "platform_linux.h" #include "platform_linux.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <memory> #include <memory>
#include "../../base/log.h" #include "../../base/log.h"
@ -26,25 +23,39 @@ void PlatformLinux::Initialize() {
root_path_ = "../../"; root_path_ = "../../";
LOG << "Root path: " << root_path_.c_str(); 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."; LOG << "Failed to initialize renderer.";
throw internal_error; throw internal_error;
} }
Display* display = renderer_->display(); XSelectInput(display_, window_,
Window window = renderer_->window();
XSelectInput(display, window,
KeyPressMask | Button1MotionMask | ButtonPressMask | KeyPressMask | Button1MotionMask | ButtonPressMask |
ButtonReleaseMask | FocusChangeMask); ButtonReleaseMask | FocusChangeMask);
Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", false); Atom WM_DELETE_WINDOW = XInternAtom(display_, "WM_DELETE_WINDOW", false);
XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1); XSetWMProtocols(display_, window_, &WM_DELETE_WINDOW, 1);
}
void PlatformLinux::Shutdown() {
PlatformBase::Shutdown();
DestroyWindow();
} }
void PlatformLinux::Update() { void PlatformLinux::Update() {
Display* display = renderer_->display(); while (XPending(display_)) {
while (XPending(display)) {
XEvent e; XEvent e;
XNextEvent(display, &e); XNextEvent(display_, &e);
switch (e.type) { switch (e.type) {
case KeyPress: { case KeyPress: {
KeySym key = XLookupKeysym(&e.xkey, 0); KeySym key = XLookupKeysym(&e.xkey, 0);
@ -90,7 +101,7 @@ void PlatformLinux::Update() {
break; break;
} }
case FocusIn: { case FocusIn: {
engine_->GainedFocus(); engine_->GainedFocus(false);
break; break;
} }
case ClientMessage: { case ClientMessage: {
@ -106,6 +117,47 @@ void PlatformLinux::Exit() {
should_exit_ = true; 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 } // namespace eng
int main(int argc, char** argv) { int main(int argc, char** argv) {

View File

@ -1,6 +1,9 @@
#ifndef PLATFORM_LINUX_H #ifndef PLATFORM_LINUX_H
#define PLATFORM_LINUX_H #define PLATFORM_LINUX_H
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "platform_base.h" #include "platform_base.h"
namespace eng { namespace eng {
@ -12,11 +15,26 @@ class PlatformLinux : public PlatformBase {
void Initialize(); void Initialize();
void Shutdown();
void Update(); void Update();
void Exit(); void Exit();
void Vibrate(int duration) {} 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 } // namespace eng

View File

@ -1,8 +1,6 @@
#include "geometry.h" #include "geometry.h"
#include "../engine.h"
#include "../mesh.h" #include "../mesh.h"
#include "render_command.h"
#include "renderer.h" #include "renderer.h"
namespace eng { namespace eng {
@ -19,30 +17,21 @@ Geometry::~Geometry() {
void Geometry::Create(std::unique_ptr<Mesh> mesh) { void Geometry::Create(std::unique_ptr<Mesh> mesh) {
Destroy(); Destroy();
valid_ = true; valid_ = true;
vertex_description_ = mesh->vertex_description(); vertex_description_ = mesh->vertex_description();
primitive_ = mesh->primitive();
auto cmd = std::make_unique<CmdCreateGeometry>(); renderer_->CreateGeometry(impl_data_, std::move(mesh));
cmd->mesh = std::move(mesh);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
} }
void Geometry::Destroy() { void Geometry::Destroy() {
if (valid_) { if (valid_) {
auto cmd = std::make_unique<CmdDestroyGeometry>(); renderer_->DestroyGeometry(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
valid_ = false; valid_ = false;
} }
} }
void Geometry::Draw() { void Geometry::Draw() {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdDrawGeometry>(); renderer_->Draw(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
} // namespace eng } // namespace eng

View File

@ -29,8 +29,11 @@ class Geometry : public RenderResource {
return vertex_description_; return vertex_description_;
} }
Primitive primitive() { return primitive_; }
private: private:
VertexDescripton vertex_description_; VertexDescripton vertex_description_;
Primitive primitive_ = kPrimitive_Invalid;
}; };
} // namespace eng } // namespace eng

View File

@ -5,12 +5,12 @@
// Use the modified Khronos header from ndk-helper. This gives access to // Use the modified Khronos header from ndk-helper. This gives access to
// additional functionality the drivers may expose but which the system headers // additional functionality the drivers may expose but which the system headers
// do not. // do not.
#include "../../third_party/android/gl3stub.h" #include "../../../third_party/android/gl3stub.h"
#include <GLES2/gl2ext.h> #include <GLES2/gl2ext.h>
#elif defined(__linux__) #elif defined(__linux__)
#include "../../third_party/glew/glew.h" #include "../../../third_party/glew/glew.h"
#include "../../third_party/glew/glxew.h" #include "../../../third_party/glew/glxew.h"
// Define the missing format for the etc1 // Define the missing format for the etc1
#ifndef GL_ETC1_RGB8_OES #ifndef GL_ETC1_RGB8_OES

View File

@ -1,8 +1,8 @@
#include "render_command.h" #include "render_command.h"
#include "../image.h" #include "../../image.h"
#include "../mesh.h" #include "../../mesh.h"
#include "../shader_source.h" #include "../../shader_source.h"
#ifdef _DEBUG #ifdef _DEBUG
#define RENDER_COMMAND_IMPL(NAME, GLOBAL) \ #define RENDER_COMMAND_IMPL(NAME, GLOBAL) \

View File

@ -5,9 +5,9 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "../../base/hash.h" #include "../../../base/hash.h"
#include "../../base/vecmath.h" #include "../../../base/vecmath.h"
#include "renderer_types.h" #include "../renderer_types.h"
namespace eng { namespace eng {
@ -39,7 +39,7 @@ struct RenderCommand {
const CommandId cmd_id = INVALID_CMD_ID; const CommandId cmd_id = INVALID_CMD_ID;
// Global render commands are guaranteed to be processed. Others commands are // 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; const bool global = false;
#ifdef _DEBUG #ifdef _DEBUG

View File

@ -1,21 +1,22 @@
#include "renderer.h" #include "renderer_opengl.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <sstream> #include <sstream>
#include <unordered_set>
#include "../../base/log.h" #include "../../../base/log.h"
#include "../../base/vecmath.h" #include "../../../base/vecmath.h"
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
#include "../../base/task_runner.h" #include "../../../base/task_runner.h"
#endif // THREADED_RENDERING #endif // THREADED_RENDERING
#include "../image.h" #include "../../image.h"
#include "../mesh.h" #include "../../mesh.h"
#include "../shader_source.h" #include "../../shader_source.h"
#include "geometry.h" #include "../geometry.h"
#include "../shader.h"
#include "../texture.h"
#include "render_command.h" #include "render_command.h"
#include "shader.h"
#include "texture.h"
using namespace base; using namespace base;
@ -29,44 +30,170 @@ constexpr GLenum kGlDataType[eng::kDataType_Max] = {
GL_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_SHORT}; GL_SHORT, GL_UNSIGNED_INT, GL_UNSIGNED_SHORT};
const std::string kAttributeNames[eng::kAttribType_Max] = { const std::string kAttributeNames[eng::kAttribType_Max] = {
"inColor", "inNormal", "inPosition", "inTexCoord"}; "in_color", "in_normal", "in_position", "in_tex_coord"};
} // namespace } // namespace
namespace eng { namespace eng {
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
Renderer::Renderer() RendererOpenGL::RendererOpenGL()
: main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {} : main_thread_task_runner_(TaskRunner::GetThreadLocalTaskRunner()) {}
#else #else
Renderer::Renderer() = default; RendererOpenGL::RendererOpenGL() = default;
#endif // THREADED_RENDERING #endif // THREADED_RENDERING
Renderer::~Renderer() = default; RendererOpenGL::~RendererOpenGL() = default;
void Renderer::SetContextLostCB(Closure cb) { void RendererOpenGL::CreateGeometry(std::shared_ptr<void> impl_data,
context_lost_cb_ = std::move(cb); std::unique_ptr<Mesh> mesh) {
auto cmd = std::make_unique<CmdCreateGeometry>();
cmd->mesh = std::move(mesh);
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
} }
void Renderer::ContextLost() { void RendererOpenGL::DestroyGeometry(std::shared_ptr<void> impl_data) {
auto cmd = std::make_unique<CmdDestroyGeometry>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::Draw(std::shared_ptr<void> impl_data) {
auto cmd = std::make_unique<CmdDrawGeometry>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::UpdateTexture(std::shared_ptr<void> impl_data,
std::unique_ptr<Image> image) {
auto cmd = std::make_unique<CmdUpdateTexture>();
cmd->image = std::move(image);
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::DestroyTexture(std::shared_ptr<void> impl_data) {
auto cmd = std::make_unique<CmdDestoryTexture>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::ActivateTexture(std::shared_ptr<void> impl_data) {
auto cmd = std::make_unique<CmdActivateTexture>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::CreateShader(std::shared_ptr<void> impl_data,
std::unique_ptr<ShaderSource> source,
const VertexDescripton& vertex_description,
Primitive primitive) {
auto cmd = std::make_unique<CmdCreateShader>();
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<void> impl_data) {
auto cmd = std::make_unique<CmdDestroyShader>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::ActivateShader(std::shared_ptr<void> impl_data) {
auto cmd = std::make_unique<CmdActivateShader>();
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector2& val) {
auto cmd = std::make_unique<CmdSetUniformVec2>();
cmd->name = name;
cmd->v = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector3& val) {
auto cmd = std::make_unique<CmdSetUniformVec3>();
cmd->name = name;
cmd->v = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector4& val) {
auto cmd = std::make_unique<CmdSetUniformVec4>();
cmd->name = name;
cmd->v = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Matrix4x4& val) {
auto cmd = std::make_unique<CmdSetUniformMat4>();
cmd->name = name;
cmd->m = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
float val) {
auto cmd = std::make_unique<CmdSetUniformFloat>();
cmd->name = name;
cmd->f = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
int val) {
auto cmd = std::make_unique<CmdSetUniformInt>();
cmd->name = name;
cmd->i = val;
cmd->impl_data = impl_data;
EnqueueCommand(std::move(cmd));
}
void RendererOpenGL::Present() {
EnqueueCommand(std::make_unique<CmdPresent>());
#ifdef THREADED_RENDERING
draw_complete_semaphore_.Acquire();
#endif // THREADED_RENDERING
fps_++;
}
void RendererOpenGL::ContextLost() {
LOG << "Context lost."; LOG << "Context lost.";
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
global_commands_.clear(); global_commands_.clear();
draw_commands_[0].clear(); draw_commands_[0].clear();
draw_commands_[1].clear(); draw_commands_[1].clear();
#endif // THREADED_RENDERING
InvalidateAllResources(); main_thread_task_runner_->PostTask(
HERE, std::bind(&RendererOpenGL::InvalidateAllResources, this));
#ifdef THREADED_RENDERING main_thread_task_runner_->PostTask(HERE, context_lost_cb_);
main_thread_task_runner_->EnqueueTask(HERE, context_lost_cb_);
#else #else
InvalidateAllResources();
context_lost_cb_(); context_lost_cb_();
#endif // THREADED_RENDERING #endif // THREADED_RENDERING
} }
std::unique_ptr<RenderResource> Renderer::CreateResource( std::unique_ptr<RenderResource> RendererOpenGL::CreateResource(
RenderResourceFactoryBase& factory) { RenderResourceFactoryBase& factory) {
static unsigned last_id = 0; static unsigned last_id = 0;
@ -88,48 +215,19 @@ std::unique_ptr<RenderResource> Renderer::CreateResource(
return resource; return resource;
} }
void Renderer::ReleaseResource(unsigned resource_id) { void RendererOpenGL::ReleaseResource(unsigned resource_id) {
auto it = resources_.find(resource_id); auto it = resources_.find(resource_id);
if (it != resources_.end()) if (it != resources_.end())
resources_.erase(it); resources_.erase(it);
} }
void Renderer::EnqueueCommand(std::unique_ptr<RenderCommand> cmd) { size_t RendererOpenGL::GetAndResetFPS() {
#ifdef THREADED_RENDERING
if (cmd->global) {
{
std::unique_lock<std::mutex> 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<std::mutex> 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() {
int ret = fps_; int ret = fps_;
fps_ = 0; fps_ = 0;
return ret; return ret;
} }
bool Renderer::InitCommon() { bool RendererOpenGL::InitCommon() {
// Get information about the currently active context. // Get information about the currently active context.
const char* renderer = const char* renderer =
reinterpret_cast<const char*>(glGetString(GL_RENDERER)); reinterpret_cast<const char*>(glGetString(GL_RENDERER));
@ -187,10 +285,8 @@ bool Renderer::InitCommon() {
} }
// Ancient hardware is not supported. // Ancient hardware is not supported.
if (!npot_) { if (!npot_)
LOG << "NPOT not supported."; LOG << "NPOT not supported.";
return false;
}
if (vertex_array_objects_) if (vertex_array_objects_)
LOG << "Supports Vertex Array Objects."; LOG << "Supports Vertex Array Objects.";
@ -211,12 +307,12 @@ bool Renderer::InitCommon() {
return true; return true;
} }
void Renderer::InvalidateAllResources() { void RendererOpenGL::InvalidateAllResources() {
for (auto& r : resources_) for (auto& r : resources_)
r.second->Destroy(); r.second->Destroy();
} }
bool Renderer::StartRenderThread() { bool RendererOpenGL::StartRenderThread() {
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
LOG << "Starting render thread."; LOG << "Starting render thread.";
@ -228,7 +324,7 @@ bool Renderer::StartRenderThread() {
std::promise<bool> promise; std::promise<bool> promise;
std::future<bool> future = promise.get_future(); std::future<bool> future = promise.get_future();
render_thread_ = render_thread_ =
std::thread(&Renderer::RenderThreadMain, this, std::move(promise)); std::thread(&RendererOpenGL::RenderThreadMain, this, std::move(promise));
future.wait(); future.wait();
return future.get(); return future.get();
#else #else
@ -237,7 +333,7 @@ bool Renderer::StartRenderThread() {
#endif // THREADED_RENDERING #endif // THREADED_RENDERING
} }
void Renderer::TerminateRenderThread() { void RendererOpenGL::TerminateRenderThread() {
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
DCHECK(!terminate_render_thread_); DCHECK(!terminate_render_thread_);
@ -256,7 +352,7 @@ void Renderer::TerminateRenderThread() {
#ifdef THREADED_RENDERING #ifdef THREADED_RENDERING
void Renderer::RenderThreadMain(std::promise<bool> promise) { void RendererOpenGL::RenderThreadMain(std::promise<bool> promise) {
promise.set_value(InitInternal()); promise.set_value(InitInternal());
std::deque<std::unique_ptr<RenderCommand>> cq[2]; std::deque<std::unique_ptr<RenderCommand>> cq[2];
@ -291,7 +387,33 @@ void Renderer::RenderThreadMain(std::promise<bool> promise) {
#endif // THREADED_RENDERING #endif // THREADED_RENDERING
void Renderer::ProcessCommand(RenderCommand* cmd) { void RendererOpenGL::EnqueueCommand(std::unique_ptr<RenderCommand> cmd) {
#ifdef THREADED_RENDERING
if (cmd->global) {
{
std::unique_lock<std::mutex> 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<std::mutex> 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 #if 0
LOG << "Processing command: " << cmd->cmd_name.c_str(); LOG << "Processing command: " << cmd->cmd_name.c_str();
#endif #endif
@ -350,7 +472,7 @@ void Renderer::ProcessCommand(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdUpdateTexture(RenderCommand* cmd) { void RendererOpenGL::HandleCmdUpdateTexture(RenderCommand* cmd) {
auto* c = static_cast<CmdUpdateTexture*>(cmd); auto* c = static_cast<CmdUpdateTexture*>(cmd);
auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get());
bool new_texture = impl_data->id == 0; 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<CmdDestoryTexture*>(cmd); auto* c = static_cast<CmdDestoryTexture*>(cmd);
auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { 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<CmdActivateTexture*>(cmd); auto* c = static_cast<CmdActivateTexture*>(cmd);
auto impl_data = reinterpret_cast<TextureOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<TextureOpenGL*>(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); 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<CmdCreateGeometry*>(cmd); auto* c = static_cast<CmdCreateGeometry*>(cmd);
auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get());
if (impl_data->vertex_buffer_id > 0) if (impl_data->vertex_buffer_id > 0)
@ -448,7 +572,7 @@ void Renderer::HandleCmdCreateGeometry(RenderCommand* cmd) {
} }
GLuint vertex_array_id = 0; GLuint vertex_array_id = 0;
if (SupportsVAO()) { if (vertex_array_objects_) {
glGenVertexArrays(1, &vertex_array_id); glGenVertexArrays(1, &vertex_array_id);
glBindVertexArray(vertex_array_id); glBindVertexArray(vertex_array_id);
} }
@ -464,7 +588,7 @@ void Renderer::HandleCmdCreateGeometry(RenderCommand* cmd) {
// set up. // set up.
std::vector<GeometryOpenGL::Element> vertex_layout; std::vector<GeometryOpenGL::Element> vertex_layout;
if (!SetupVertexLayout(c->mesh->vertex_description(), vertex_size, if (!SetupVertexLayout(c->mesh->vertex_description(), vertex_size,
SupportsVAO(), vertex_layout)) vertex_array_objects_, vertex_layout))
return; return;
// Create the index buffer and upload the data. // Create the index buffer and upload the data.
@ -497,7 +621,7 @@ void Renderer::HandleCmdCreateGeometry(RenderCommand* cmd) {
index_buffer_id}; index_buffer_id};
} }
void Renderer::HandleCmdDestroyGeometry(RenderCommand* cmd) { void RendererOpenGL::HandleCmdDestroyGeometry(RenderCommand* cmd) {
auto* c = static_cast<CmdDestroyGeometry*>(cmd); auto* c = static_cast<CmdDestroyGeometry*>(cmd);
auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get());
if (impl_data->vertex_buffer_id == 0) if (impl_data->vertex_buffer_id == 0)
@ -513,7 +637,7 @@ void Renderer::HandleCmdDestroyGeometry(RenderCommand* cmd) {
*impl_data = {}; *impl_data = {};
} }
void Renderer::HandleCmdDrawGeometry(RenderCommand* cmd) { void RendererOpenGL::HandleCmdDrawGeometry(RenderCommand* cmd) {
auto* c = static_cast<CmdDrawGeometry*>(cmd); auto* c = static_cast<CmdDrawGeometry*>(cmd);
auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<GeometryOpenGL*>(c->impl_data.get());
if (impl_data->vertex_buffer_id == 0) if (impl_data->vertex_buffer_id == 0)
@ -559,7 +683,7 @@ void Renderer::HandleCmdDrawGeometry(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdCreateShader(RenderCommand* cmd) { void RendererOpenGL::HandleCmdCreateShader(RenderCommand* cmd) {
auto* c = static_cast<CmdCreateShader*>(cmd); auto* c = static_cast<CmdCreateShader*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) if (impl_data->id > 0)
@ -604,7 +728,7 @@ void Renderer::HandleCmdCreateShader(RenderCommand* cmd) {
*impl_data = {id, {}}; *impl_data = {id, {}};
} }
void Renderer::HandleCmdDestroyShader(RenderCommand* cmd) { void RendererOpenGL::HandleCmdDestroyShader(RenderCommand* cmd) {
auto* c = static_cast<CmdDestroyShader*>(cmd); auto* c = static_cast<CmdDestroyShader*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -613,14 +737,16 @@ void Renderer::HandleCmdDestroyShader(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdActivateShader(RenderCommand* cmd) { void RendererOpenGL::HandleCmdActivateShader(RenderCommand* cmd) {
auto* c = static_cast<CmdActivateShader*>(cmd); auto* c = static_cast<CmdActivateShader*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) if (impl_data->id > 0 && impl_data->id != active_shader_id_) {
glUseProgram(impl_data->id); glUseProgram(impl_data->id);
active_shader_id_ = impl_data->id;
}
} }
void Renderer::HandleCmdSetUniformVec2(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformVec2(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformVec2*>(cmd); auto* c = static_cast<CmdSetUniformVec2*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -631,7 +757,7 @@ void Renderer::HandleCmdSetUniformVec2(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdSetUniformVec3(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformVec3(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformVec3*>(cmd); auto* c = static_cast<CmdSetUniformVec3*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -642,7 +768,7 @@ void Renderer::HandleCmdSetUniformVec3(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdSetUniformVec4(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformVec4(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformVec4*>(cmd); auto* c = static_cast<CmdSetUniformVec4*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -653,7 +779,7 @@ void Renderer::HandleCmdSetUniformVec4(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdSetUniformMat4(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformMat4(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformMat4*>(cmd); auto* c = static_cast<CmdSetUniformMat4*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -664,7 +790,7 @@ void Renderer::HandleCmdSetUniformMat4(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdSetUniformFloat(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformFloat(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformFloat*>(cmd); auto* c = static_cast<CmdSetUniformFloat*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -675,7 +801,7 @@ void Renderer::HandleCmdSetUniformFloat(RenderCommand* cmd) {
} }
} }
void Renderer::HandleCmdSetUniformInt(RenderCommand* cmd) { void RendererOpenGL::HandleCmdSetUniformInt(RenderCommand* cmd) {
auto* c = static_cast<CmdSetUniformInt*>(cmd); auto* c = static_cast<CmdSetUniformInt*>(cmd);
auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get()); auto impl_data = reinterpret_cast<ShaderOpenGL*>(c->impl_data.get());
if (impl_data->id > 0) { if (impl_data->id > 0) {
@ -686,7 +812,7 @@ void Renderer::HandleCmdSetUniformInt(RenderCommand* cmd) {
} }
} }
bool Renderer::SetupVertexLayout( bool RendererOpenGL::SetupVertexLayout(
const VertexDescripton& vd, const VertexDescripton& vd,
GLuint vertex_size, GLuint vertex_size,
bool use_vao, bool use_vao,
@ -726,7 +852,7 @@ bool Renderer::SetupVertexLayout(
return true; return true;
} }
GLuint Renderer::CreateShader(const char* source, GLenum type) { GLuint RendererOpenGL::CreateShader(const char* source, GLenum type) {
GLuint shader = glCreateShader(type); GLuint shader = glCreateShader(type);
if (shader) { if (shader) {
glShaderSource(shader, 1, &source, NULL); glShaderSource(shader, 1, &source, NULL);
@ -751,7 +877,8 @@ GLuint Renderer::CreateShader(const char* source, GLenum type) {
return shader; return shader;
} }
bool Renderer::BindAttributeLocation(GLuint id, const VertexDescripton& vd) { bool RendererOpenGL::BindAttributeLocation(GLuint id,
const VertexDescripton& vd) {
int current = 0; int current = 0;
int tex_coord = 0; int tex_coord = 0;
@ -765,7 +892,7 @@ bool Renderer::BindAttributeLocation(GLuint id, const VertexDescripton& vd) {
return current > 0; return current > 0;
} }
GLint Renderer::GetUniformLocation( GLint RendererOpenGL::GetUniformLocation(
GLuint id, GLuint id,
const std::string& name, const std::string& name,
std::unordered_map<std::string, GLuint>& uniforms) { std::unordered_map<std::string, GLuint>& uniforms) {

View File

@ -0,0 +1,215 @@
#ifndef RENDERER_OPENGL_H
#define RENDERER_OPENGL_H
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#define THREADED_RENDERING
#ifdef THREADED_RENDERING
#include <condition_variable>
#include <deque>
#include <future>
#include <mutex>
#include <thread>
#include "../../../base/semaphore.h"
#endif // THREADED_RENDERING
#include "opengl.h"
#include "../render_resource.h"
#include "../renderer.h"
#if defined(__ANDROID__)
struct ANativeWindow;
#endif
#ifdef THREADED_RENDERING
namespace base {
class TaskRunner;
}
#endif // THREADED_RENDERING
namespace eng {
struct RenderCommand;
class RendererOpenGL : public Renderer {
public:
RendererOpenGL();
~RendererOpenGL() override;
#if defined(__ANDROID__)
bool Initialize(ANativeWindow* window) override;
#elif defined(__linux__)
bool Initialize(Display* display, Window window) override;
#endif
void Shutdown() override;
void CreateGeometry(std::shared_ptr<void> impl_data,
std::unique_ptr<Mesh> mesh) override;
void DestroyGeometry(std::shared_ptr<void> impl_data) override;
void Draw(std::shared_ptr<void> impl_data) override;
void UpdateTexture(std::shared_ptr<void> impl_data,
std::unique_ptr<Image> image) override;
void DestroyTexture(std::shared_ptr<void> impl_data) override;
void ActivateTexture(std::shared_ptr<void> impl_data) override;
void CreateShader(std::shared_ptr<void> impl_data,
std::unique_ptr<ShaderSource> source,
const VertexDescripton& vertex_description,
Primitive primitive) override;
void DestroyShader(std::shared_ptr<void> impl_data) override;
void ActivateShader(std::shared_ptr<void> impl_data) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector2& val) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector3& val) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector4& val) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Matrix4x4& val) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
float val) override;
void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
int val) override;
void UploadUniforms(std::shared_ptr<void> impl_data) override {}
void PrepareForDrawing() override {}
void Present() override;
std::unique_ptr<RenderResource> CreateResource(
RenderResourceFactoryBase& factory) override;
void ReleaseResource(unsigned resource_id) override;
size_t GetAndResetFPS() override;
#if defined(__linux__) && !defined(__ANDROID__)
XVisualInfo* GetXVisualInfo(Display* display) override;
#endif
private:
struct GeometryOpenGL {
struct Element {
GLsizei num_elements;
GLenum type;
size_t vertex_offset;
};
GLsizei num_vertices = 0;
GLsizei num_indices = 0;
GLenum primitive = 0;
GLenum index_type = 0;
std::vector<Element> vertex_layout;
GLuint vertex_size = 0;
GLuint vertex_array_id = 0;
GLuint vertex_buffer_id = 0;
GLuint index_buffer_id = 0;
};
struct ShaderOpenGL {
GLuint id = 0;
std::unordered_map<std::string, GLuint> uniforms;
};
struct TextureOpenGL {
GLuint id = 0;
};
GLuint active_shader_id_ = 0;
GLuint active_texture_id_ = 0;
bool vertex_array_objects_ = false;
bool npot_ = false;
std::unordered_map<unsigned, RenderResource*> resources_;
#ifdef THREADED_RENDERING
// Global commands are independent from frames and guaranteed to be processed.
std::deque<std::unique_ptr<RenderCommand>> global_commands_;
// Draw commands are fame specific and can be discarded if the rendering is
// not throttled.
std::deque<std::unique_ptr<RenderCommand>> draw_commands_[2];
std::condition_variable cv_;
std::mutex mutex_;
std::thread render_thread_;
bool terminate_render_thread_ = false;
base::Semaphore draw_complete_semaphore_;
base::TaskRunner* main_thread_task_runner_;
#endif // THREADED_RENDERING
// Stats.
size_t fps_ = 0;
#if defined(__ANDROID__)
ANativeWindow* window_;
#elif defined(__linux__)
Display* display_ = NULL;
Window window_ = 0;
GLXContext glx_context_ = NULL;
#endif
bool InitInternal();
bool InitCommon();
void ShutdownInternal();
void ContextLost();
void InvalidateAllResources();
bool StartRenderThread();
void TerminateRenderThread();
#ifdef THREADED_RENDERING
void RenderThreadMain(std::promise<bool> promise);
#endif // THREADED_RENDERING
void EnqueueCommand(std::unique_ptr<RenderCommand> cmd);
void ProcessCommand(RenderCommand* cmd);
void HandleCmdPresent(RenderCommand* cmd);
void HandleCmdUpdateTexture(RenderCommand* cmd);
void HandleCmdDestoryTexture(RenderCommand* cmd);
void HandleCmdActivateTexture(RenderCommand* cmd);
void HandleCmdCreateGeometry(RenderCommand* cmd);
void HandleCmdDestroyGeometry(RenderCommand* cmd);
void HandleCmdDrawGeometry(RenderCommand* cmd);
void HandleCmdCreateShader(RenderCommand* cmd);
void HandleCmdDestroyShader(RenderCommand* cmd);
void HandleCmdActivateShader(RenderCommand* cmd);
void HandleCmdSetUniformVec2(RenderCommand* cmd);
void HandleCmdSetUniformVec3(RenderCommand* cmd);
void HandleCmdSetUniformVec4(RenderCommand* cmd);
void HandleCmdSetUniformMat4(RenderCommand* cmd);
void HandleCmdSetUniformFloat(RenderCommand* cmd);
void HandleCmdSetUniformInt(RenderCommand* cmd);
bool SetupVertexLayout(const VertexDescripton& vd,
GLuint vertex_size,
bool use_vao,
std::vector<GeometryOpenGL::Element>& vertex_layout);
GLuint CreateShader(const char* source, GLenum type);
bool BindAttributeLocation(GLuint id, const VertexDescripton& vd);
GLint GetUniformLocation(GLuint id,
const std::string& name,
std::unordered_map<std::string, GLuint>& uniforms);
};
} // namespace eng
#endif // RENDERER_OPENGL_H

View File

@ -1,20 +1,20 @@
#include "renderer.h" #include "renderer_opengl.h"
#include <android/native_window.h> #include <android/native_window.h>
#include "../../base/log.h" #include "../../../base/log.h"
#include "../../third_party/android/GLContext.h" #include "../../../third_party/android/GLContext.h"
namespace eng { namespace eng {
bool Renderer::Initialize(ANativeWindow* window) { bool RendererOpenGL::Initialize(ANativeWindow* window) {
LOG << "Initializing renderer."; LOG << "Initializing renderer.";
window_ = window; window_ = window;
return StartRenderThread(); return StartRenderThread();
} }
void Renderer::Shutdown() { void RendererOpenGL::Shutdown() {
if (terminate_render_thread_) if (terminate_render_thread_)
return; return;
@ -23,7 +23,7 @@ void Renderer::Shutdown() {
TerminateRenderThread(); TerminateRenderThread();
} }
bool Renderer::InitInternal() { bool RendererOpenGL::InitInternal() {
ndk_helper::GLContext* gl_context = ndk_helper::GLContext::GetInstance(); ndk_helper::GLContext* gl_context = ndk_helper::GLContext::GetInstance();
if (!gl_context->IsInitialzed()) { if (!gl_context->IsInitialzed()) {
@ -52,16 +52,21 @@ bool Renderer::InitInternal() {
return InitCommon(); return InitCommon();
} }
void Renderer::ShutdownInternal() { void RendererOpenGL::ShutdownInternal() {
ndk_helper::GLContext::GetInstance()->Suspend(); ndk_helper::GLContext::GetInstance()->Suspend();
} }
void Renderer::HandleCmdPresent(RenderCommand* cmd) { void RendererOpenGL::HandleCmdPresent(RenderCommand* cmd) {
if (EGL_SUCCESS != ndk_helper::GLContext::GetInstance()->Swap()) { if (EGL_SUCCESS != ndk_helper::GLContext::GetInstance()->Swap()) {
ContextLost(); ContextLost();
return; return;
} }
#ifdef THREADED_RENDERING
draw_complete_semaphore_.Release();
#endif // THREADED_RENDERING
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
active_shader_id_ = 0;
active_texture_id_ = 0;
} }
} // namespace eng } // namespace eng

View File

@ -0,0 +1,73 @@
#include "renderer_opengl.h"
#include "../../../base/log.h"
namespace eng {
bool RendererOpenGL::Initialize(Display* display, Window window) {
LOG << "Initializing renderer.";
display_ = display;
window_ = window;
XWindowAttributes xwa;
XGetWindowAttributes(display_, window_, &xwa);
screen_width_ = xwa.width;
screen_height_ = xwa.height;
return StartRenderThread();
}
void RendererOpenGL::Shutdown() {
LOG << "Shutting down renderer.";
TerminateRenderThread();
}
bool RendererOpenGL::InitInternal() {
// Create the OpenGL context.
glx_context_ =
glXCreateContext(display_, GetXVisualInfo(display_), NULL, GL_TRUE);
if (!glx_context_) {
LOG << "Couldn't create the glx context.";
return false;
}
glXMakeCurrent(display_, window_, glx_context_);
if (GLEW_OK != glewInit()) {
LOG << "Couldn't initialize OpenGL extension wrangler.";
return false;
}
return InitCommon();
}
void RendererOpenGL::ShutdownInternal() {
if (display_ && glx_context_) {
glXMakeCurrent(display_, None, NULL);
glXDestroyContext(display_, glx_context_);
glx_context_ = nullptr;
}
}
void RendererOpenGL::HandleCmdPresent(RenderCommand* cmd) {
if (display_) {
glXSwapBuffers(display_, window_);
#ifdef THREADED_RENDERING
draw_complete_semaphore_.Release();
#endif // THREADED_RENDERING
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
active_shader_id_ = 0;
active_texture_id_ = 0;
}
}
XVisualInfo* RendererOpenGL::GetXVisualInfo(Display* display) {
// Look for the right visual to set up the OpenGL context.
GLint glx_attributes[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER,
None};
return glXChooseVisual(display, 0, glx_attributes);
}
} // namespace eng

View File

@ -1,66 +1,88 @@
#ifndef RENDERER_H #ifndef RENDERER_H
#define RENDERER_H #define RENDERER_H
#include <array>
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define THREADED_RENDERING #if defined(__linux__) && !defined(__ANDROID__)
#include <X11/Xlib.h>
#ifdef THREADED_RENDERING #include <X11/Xutil.h>
#include <condition_variable> #endif
#include <deque>
#include <future>
#include <mutex>
#include <thread>
#endif // THREADED_RENDERING
#include "opengl.h"
#include "../../base/closure.h" #include "../../base/closure.h"
#include "render_resource.h" #include "../../base/vecmath.h"
#include "renderer_types.h" #include "renderer_types.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
struct ANativeWindow; struct ANativeWindow;
#endif #endif
#ifdef THREADED_RENDERING
namespace base {
class TaskRunner;
}
#endif // THREADED_RENDERING
namespace eng { namespace eng {
struct RenderCommand;
class Image; class Image;
class ShaderSource;
class Mesh;
class Renderer { class Renderer {
public: public:
Renderer(); const unsigned kInvalidId = 0;
~Renderer();
void SetContextLostCB(base::Closure cb); Renderer() = default;
virtual ~Renderer() = default;
void SetContextLostCB(base::Closure cb) { context_lost_cb_ = std::move(cb); }
#if defined(__ANDROID__) #if defined(__ANDROID__)
bool Initialize(ANativeWindow* window); virtual bool Initialize(ANativeWindow* window) = 0;
#elif defined(__linux__) #elif defined(__linux__)
bool Initialize(); virtual bool Initialize(Display* display, Window window) = 0;
#endif #endif
void Shutdown(); virtual void Shutdown() = 0;
void ContextLost(); virtual void CreateGeometry(std::shared_ptr<void> impl_data,
std::unique_ptr<Mesh> mesh) = 0;
virtual void DestroyGeometry(std::shared_ptr<void> impl_data) = 0;
virtual void Draw(std::shared_ptr<void> impl_data) = 0;
std::unique_ptr<RenderResource> CreateResource( virtual void UpdateTexture(std::shared_ptr<void> impl_data,
RenderResourceFactoryBase& factory); std::unique_ptr<Image> image) = 0;
void ReleaseResource(unsigned resource_id); virtual void DestroyTexture(std::shared_ptr<void> impl_data) = 0;
virtual void ActivateTexture(std::shared_ptr<void> impl_data) = 0;
void EnqueueCommand(std::unique_ptr<RenderCommand> cmd); virtual void CreateShader(std::shared_ptr<void> impl_data,
std::unique_ptr<ShaderSource> source,
const VertexDescripton& vertex_description,
Primitive primitive) = 0;
virtual void DestroyShader(std::shared_ptr<void> impl_data) = 0;
virtual void ActivateShader(std::shared_ptr<void> impl_data) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector2& val) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector3& val) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Vector4& val) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
const base::Matrix4x4& val) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
float val) = 0;
virtual void SetUniform(std::shared_ptr<void> impl_data,
const std::string& name,
int val) = 0;
virtual void UploadUniforms(std::shared_ptr<void> impl_data) = 0;
virtual void PrepareForDrawing() = 0;
virtual void Present() = 0;
virtual std::unique_ptr<RenderResource> CreateResource(
RenderResourceFactoryBase& factory) = 0;
virtual void ReleaseResource(unsigned resource_id) = 0;
bool SupportsETC1() const { return texture_compression_.etc1; } bool SupportsETC1() const { return texture_compression_.etc1; }
bool SupportsDXT1() const { bool SupportsDXT1() const {
@ -69,22 +91,16 @@ class Renderer {
bool SupportsDXT5() const { return texture_compression_.s3tc; } bool SupportsDXT5() const { return texture_compression_.s3tc; }
bool SupportsATC() const { return texture_compression_.atc; } bool SupportsATC() const { return texture_compression_.atc; }
bool SupportsVAO() const { return vertex_array_objects_; }
int screen_width() const { return screen_width_; } int screen_width() const { return screen_width_; }
int screen_height() const { return screen_height_; } int screen_height() const { return screen_height_; }
size_t GetAndResetFPS(); virtual size_t GetAndResetFPS() = 0;
size_t global_queue_size() { return global_queue_size_; }
size_t render_queue_size() { return render_queue_size_; }
#if defined(__linux__) && !defined(__ANDROID__) #if defined(__linux__) && !defined(__ANDROID__)
Display* display() { return display_; } virtual XVisualInfo* GetXVisualInfo(Display* display) = 0;
Window window() { return window_; }
#endif #endif
private: protected:
struct TextureCompression { struct TextureCompression {
unsigned etc1 : 1; unsigned etc1 : 1;
unsigned dxt1 : 1; unsigned dxt1 : 1;
@ -102,119 +118,12 @@ class Renderer {
atc(false) {} atc(false) {}
}; };
struct GeometryOpenGL {
struct Element {
GLsizei num_elements;
GLenum type;
size_t vertex_offset;
};
GLsizei num_vertices = 0;
GLsizei num_indices = 0;
GLenum primitive = 0;
GLenum index_type = 0;
std::vector<Element> vertex_layout;
GLuint vertex_size = 0;
GLuint vertex_array_id = 0;
GLuint vertex_buffer_id = 0;
GLuint index_buffer_id = 0;
};
struct ShaderOpenGL {
GLuint id = 0;
std::unordered_map<std::string, GLuint> uniforms;
};
struct TextureOpenGL {
GLuint id = 0;
};
base::Closure context_lost_cb_;
TextureCompression texture_compression_; TextureCompression texture_compression_;
bool vertex_array_objects_ = false;
bool npot_ = false;
int screen_width_ = 0; int screen_width_ = 0;
int screen_height_ = 0; int screen_height_ = 0;
std::unordered_map<unsigned, RenderResource*> resources_; base::Closure context_lost_cb_;
#ifdef THREADED_RENDERING
// Global commands are independent from frames and guaranteed to be processed.
std::deque<std::unique_ptr<RenderCommand>> global_commands_;
// Draw commands are fame specific and can be discarded if the renderer deems
// frame drop.
std::deque<std::unique_ptr<RenderCommand>> draw_commands_[2];
std::condition_variable cv_;
std::mutex mutex_;
std::thread render_thread_;
bool terminate_render_thread_ = false;
base::TaskRunner* main_thread_task_runner_;
#endif // THREADED_RENDERING
// Stats.
size_t fps_ = 0;
size_t global_queue_size_ = 0;
size_t render_queue_size_ = 0;
#if defined(__ANDROID__)
ANativeWindow* window_;
#elif defined(__linux__)
Display* display_ = NULL;
Window window_ = 0;
XVisualInfo* visual_info_;
GLXContext glx_context_ = NULL;
#endif
bool InitInternal();
bool InitCommon();
void ShutdownInternal();
void InvalidateAllResources();
bool StartRenderThread();
void TerminateRenderThread();
#ifdef THREADED_RENDERING
void RenderThreadMain(std::promise<bool> promise);
#endif // THREADED_RENDERING
void ProcessCommand(RenderCommand* cmd);
void HandleCmdPresent(RenderCommand* cmd);
void HandleCmdUpdateTexture(RenderCommand* cmd);
void HandleCmdDestoryTexture(RenderCommand* cmd);
void HandleCmdActivateTexture(RenderCommand* cmd);
void HandleCmdCreateGeometry(RenderCommand* cmd);
void HandleCmdDestroyGeometry(RenderCommand* cmd);
void HandleCmdDrawGeometry(RenderCommand* cmd);
void HandleCmdCreateShader(RenderCommand* cmd);
void HandleCmdDestroyShader(RenderCommand* cmd);
void HandleCmdActivateShader(RenderCommand* cmd);
void HandleCmdSetUniformVec2(RenderCommand* cmd);
void HandleCmdSetUniformVec3(RenderCommand* cmd);
void HandleCmdSetUniformVec4(RenderCommand* cmd);
void HandleCmdSetUniformMat4(RenderCommand* cmd);
void HandleCmdSetUniformFloat(RenderCommand* cmd);
void HandleCmdSetUniformInt(RenderCommand* cmd);
bool SetupVertexLayout(const VertexDescripton& vd,
GLuint vertex_size,
bool use_vao,
std::vector<GeometryOpenGL::Element>& vertex_layout);
GLuint CreateShader(const char* source, GLenum type);
bool BindAttributeLocation(GLuint id, const VertexDescripton& vd);
GLint GetUniformLocation(GLuint id,
const std::string& name,
std::unordered_map<std::string, GLuint>& uniforms);
#if defined(__linux__) && !defined(__ANDROID__)
bool CreateWindow();
void DestroyWindow();
#endif
Renderer(const Renderer&) = delete; Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete; Renderer& operator=(const Renderer&) = delete;

View File

@ -1,101 +0,0 @@
#include "renderer.h"
#include "../../base/log.h"
namespace eng {
bool Renderer::Initialize() {
LOG << "Initializing renderer.";
if (!CreateWindow())
return false;
return StartRenderThread();
}
void Renderer::Shutdown() {
LOG << "Shutting down renderer.";
TerminateRenderThread();
DestroyWindow();
}
bool Renderer::CreateWindow() {
screen_width_ = 1280;
screen_height_ = 1024;
// 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_);
// Look for the right visual to set up the OpenGL context.
GLint glx_attributes[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER,
None};
visual_info_ = glXChooseVisual(display_, 0, glx_attributes);
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, screen_width_, screen_height_,
0, visual_info_->depth, InputOutput, visual_info_->visual,
CWColormap | CWEventMask, &window_attributes);
XMapWindow(display_, window_);
XStoreName(display_, window_, "kaliber");
return true;
}
bool Renderer::InitInternal() {
// Create the OpenGL context.
glx_context_ = glXCreateContext(display_, visual_info_, NULL, GL_TRUE);
if (!glx_context_) {
LOG << "Couldn't create the glx context.";
return false;
}
glXMakeCurrent(display_, window_, glx_context_);
if (GLEW_OK != glewInit()) {
LOG << "Couldn't initialize OpenGL extension wrangler.";
return false;
}
return InitCommon();
}
void Renderer::DestroyWindow() {
if (display_) {
XDestroyWindow(display_, window_);
XCloseDisplay(display_);
}
}
void Renderer::ShutdownInternal() {
if (display_ && glx_context_) {
glXMakeCurrent(display_, None, NULL);
glXDestroyContext(display_, glx_context_);
glx_context_ = nullptr;
}
}
void Renderer::HandleCmdPresent(RenderCommand* cmd) {
if (display_) {
glXSwapBuffers(display_, window_);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
}
} // namespace eng

View File

@ -6,6 +6,8 @@
namespace { namespace {
// Used to parse the vertex layout,
// e.g. "p3f;c4b" for "position 3 floats, color 4 bytes".
const char kLayoutDelimiter[] = ";/ \t"; const char kLayoutDelimiter[] = ";/ \t";
} // namespace } // namespace

View File

@ -8,6 +8,7 @@
namespace eng { namespace eng {
enum Primitive { enum Primitive {
kPrimitive_Invalid = -1,
kPrimitive_Triangles, kPrimitive_Triangles,
kPrimitive_TriangleStrip, kPrimitive_TriangleStrip,
kPrimitive_Max kPrimitive_Max

View File

@ -1,7 +1,6 @@
#include "shader.h" #include "shader.h"
#include "../shader_source.h" #include "../shader_source.h"
#include "render_command.h"
#include "renderer.h" #include "renderer.h"
using namespace base; using namespace base;
@ -18,92 +17,58 @@ Shader::~Shader() {
} }
void Shader::Create(std::unique_ptr<ShaderSource> source, void Shader::Create(std::unique_ptr<ShaderSource> source,
const VertexDescripton& vd) { const VertexDescripton& vd,
Primitive primitive) {
Destroy(); Destroy();
valid_ = true; valid_ = true;
renderer_->CreateShader(impl_data_, std::move(source), vd, primitive);
auto cmd = std::make_unique<CmdCreateShader>();
cmd->source = std::move(source);
cmd->vertex_description = vd;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
} }
void Shader::Destroy() { void Shader::Destroy() {
if (valid_) { if (valid_) {
auto cmd = std::make_unique<CmdDestroyShader>(); renderer_->DestroyShader(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
valid_ = false; valid_ = false;
} }
} }
void Shader::Activate() { void Shader::Activate() {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdActivateShader>(); renderer_->ActivateShader(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, const Vector2& v) { void Shader::SetUniform(const std::string& name, const Vector2& v) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformVec2>(); renderer_->SetUniform(impl_data_, name, v);
cmd->name = name;
cmd->v = v;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, const Vector3& v) { void Shader::SetUniform(const std::string& name, const Vector3& v) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformVec3>(); renderer_->SetUniform(impl_data_, name, v);
cmd->name = name;
cmd->v = v;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, const Vector4& v) { void Shader::SetUniform(const std::string& name, const Vector4& v) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformVec4>(); renderer_->SetUniform(impl_data_, name, v);
cmd->name = name;
cmd->v = v;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, const Matrix4x4& m) { void Shader::SetUniform(const std::string& name, const Matrix4x4& m) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformMat4>(); renderer_->SetUniform(impl_data_, name, m);
cmd->name = name;
cmd->m = m;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, float f) { void Shader::SetUniform(const std::string& name, float f) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformFloat>(); renderer_->SetUniform(impl_data_, name, f);
cmd->name = name;
cmd->f = f;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
void Shader::SetUniform(const std::string& name, int i) { void Shader::SetUniform(const std::string& name, int i) {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdSetUniformInt>(); renderer_->SetUniform(impl_data_, name, i);
cmd->name = name;
cmd->i = i;
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
} }
void Shader::UploadUniforms() {
if (valid_)
renderer_->UploadUniforms(impl_data_);
} }
} // namespace eng } // namespace eng

View File

@ -20,7 +20,9 @@ class Shader : public RenderResource {
Renderer* renderer); Renderer* renderer);
~Shader() override; ~Shader() override;
void Create(std::unique_ptr<ShaderSource> source, const VertexDescripton& vd); void Create(std::unique_ptr<ShaderSource> source,
const VertexDescripton& vd,
Primitive primitive);
void Destroy() override; void Destroy() override;
@ -32,6 +34,8 @@ class Shader : public RenderResource {
void SetUniform(const std::string& name, const base::Matrix4x4& m); void SetUniform(const std::string& name, const base::Matrix4x4& m);
void SetUniform(const std::string& name, float f); void SetUniform(const std::string& name, float f);
void SetUniform(const std::string& name, int i); void SetUniform(const std::string& name, int i);
void UploadUniforms();
}; };
} // namespace eng } // namespace eng

View File

@ -2,7 +2,6 @@
#include "../../base/log.h" #include "../../base/log.h"
#include "../image.h" #include "../image.h"
#include "render_command.h"
#include "renderer.h" #include "renderer.h"
namespace eng { namespace eng {
@ -20,29 +19,20 @@ void Texture::Update(std::unique_ptr<Image> image) {
valid_ = true; valid_ = true;
width_ = image->GetWidth(); width_ = image->GetWidth();
height_ = image->GetHeight(); height_ = image->GetHeight();
renderer_->UpdateTexture(impl_data_, std::move(image));
auto cmd = std::make_unique<CmdUpdateTexture>();
cmd->image = std::move(image);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
} }
void Texture::Destroy() { void Texture::Destroy() {
if (valid_) { if (valid_) {
auto cmd = std::make_unique<CmdDestoryTexture>(); renderer_->DestroyTexture(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
valid_ = false; valid_ = false;
DLOG << "Texture destroyed. resource_id: " << resource_id_; DLOG << "Texture destroyed. resource_id: " << resource_id_;
} }
} }
void Texture::Activate() { void Texture::Activate() {
if (valid_) { if (valid_)
auto cmd = std::make_unique<CmdActivateTexture>(); renderer_->ActivateTexture(impl_data_);
cmd->impl_data = impl_data_;
renderer_->EnqueueCommand(std::move(cmd));
}
} }
} // namespace eng } // namespace eng

View File

@ -11,12 +11,13 @@ namespace eng {
bool ShaderSource::Load(const std::string& name) { bool ShaderSource::Load(const std::string& name) {
Engine& engine = Engine::Get(); Engine& engine = Engine::Get();
size_t size = 0; name_ = name;
std::string vertex_file_name = name; std::string vertex_file_name = name;
vertex_file_name += "_vertex"; vertex_file_name += "_vertex";
auto vertex_source = AssetFile::ReadWholeFile( auto vertex_source = AssetFile::ReadWholeFile(vertex_file_name.c_str(),
vertex_file_name.c_str(), engine.GetRootPath().c_str(), &size, true); engine.GetRootPath().c_str(),
&vertex_source_size_, true);
if (!vertex_source) { if (!vertex_source) {
LOG << "Failed to read file: " << vertex_file_name; LOG << "Failed to read file: " << vertex_file_name;
return false; return false;
@ -26,13 +27,16 @@ bool ShaderSource::Load(const std::string& name) {
std::string fragment_file_name = name; std::string fragment_file_name = name;
fragment_file_name += "_fragment"; fragment_file_name += "_fragment";
auto fragment_source = AssetFile::ReadWholeFile( auto fragment_source = AssetFile::ReadWholeFile(fragment_file_name.c_str(),
fragment_file_name.c_str(), engine.GetRootPath().c_str(), &size, true); engine.GetRootPath().c_str(),
&fragment_source_size_, true);
if (!fragment_source) { if (!fragment_source) {
LOG << "Failed to read file: " << fragment_file_name; LOG << "Failed to read file: " << fragment_file_name;
return false; return false;
} }
LOG << "Loaded " << name;
fragment_source_ = std::move(fragment_source); fragment_source_ = std::move(fragment_source);
return true; return true;

View File

@ -16,9 +16,19 @@ class ShaderSource {
const char* GetVertexSource() const { return vertex_source_.get(); } const char* GetVertexSource() const { return vertex_source_.get(); }
const char* GetFragmentSource() const { return fragment_source_.get(); } const char* GetFragmentSource() const { return fragment_source_.get(); }
size_t vertex_source_size() const { return vertex_source_size_; }
size_t fragment_source_size() const { return fragment_source_size_ ; }
const std::string& name() const { return name_; }
private: private:
std::string name_;
std::unique_ptr<char[]> vertex_source_; std::unique_ptr<char[]> vertex_source_;
std::unique_ptr<char[]> fragment_source_; std::unique_ptr<char[]> fragment_source_;
size_t vertex_source_size_ = 0;
size_t fragment_source_size_ = 0;
}; };
} // namespace eng } // namespace eng

View File

@ -15,12 +15,12 @@ void SolidQuad::Draw(float frame_frac) {
Shader* shader = Engine::Get().GetSolidShader(); Shader* shader = Engine::Get().GetSolidShader();
shader->Activate(); shader->Activate();
shader->SetUniform("offset", offset_); shader->SetUniform("offset", position_);
shader->SetUniform("scale", scale_); shader->SetUniform("scale", GetSize());
shader->SetUniform("pivot", pivot_);
shader->SetUniform("rotation", rotation_); shader->SetUniform("rotation", rotation_);
shader->SetUniform("projection", Engine::Get().GetProjectionMarix()); shader->SetUniform("projection", Engine::Get().GetProjectionMatrix());
shader->SetUniform("color", color_); shader->SetUniform("color", color_);
shader->UploadUniforms();
Engine::Get().GetQuad()->Draw(); Engine::Get().GetQuad()->Draw();
} }

View File

@ -3,13 +3,13 @@
#include <array> #include <array>
#include "../base/log.h" #include "../base/log.h"
#include "../base/sinc_resampler.h"
#define MINIMP3_ONLY_MP3 #define MINIMP3_ONLY_MP3
#define MINIMP3_ONLY_SIMD #define MINIMP3_ONLY_SIMD
#define MINIMP3_FLOAT_OUTPUT #define MINIMP3_FLOAT_OUTPUT
#define MINIMP3_NO_STDIO #define MINIMP3_NO_STDIO
#define MINIMP3_IMPLEMENTATION #define MINIMP3_IMPLEMENTATION
#include "../third_party/minimp3/minimp3_ex.h" #include "../third_party/minimp3/minimp3_ex.h"
#include "../third_party/r8b/CDSPResampler.h"
#include "engine.h" #include "engine.h"
#include "platform/asset_file.h" #include "platform/asset_file.h"
@ -27,22 +27,37 @@ std::array<std::unique_ptr<T[]>, 2> Deinterleave(size_t num_channels,
if (num_channels == 1) { if (num_channels == 1) {
// Single channel. // Single channel.
if constexpr (std::is_same<float, T>::value) {
channels[0] = std::make_unique<T[]>(num_samples);
memcpy(channels[0].get(), input_buffer, num_samples * sizeof(float));
} else {
channels[0] = std::make_unique<T[]>(num_samples); channels[0] = std::make_unique<T[]>(num_samples);
for (int i = 0; i < num_samples; ++i) for (int i = 0; i < num_samples; ++i)
channels[0].get()[i] = input_buffer[i]; channels[0].get()[i] = static_cast<T>(input_buffer[i]);
}
} else { } else {
// Deinterleave into separate channels. // Deinterleave into separate channels.
channels[0] = std::make_unique<T[]>(num_samples); channels[0] = std::make_unique<T[]>(num_samples);
channels[1] = std::make_unique<T[]>(num_samples); channels[1] = std::make_unique<T[]>(num_samples);
for (int i = 0, j = 0; i < num_samples * 2; i += 2) { for (int i = 0, j = 0; i < num_samples * 2; i += 2) {
channels[0].get()[j] = input_buffer[i]; channels[0].get()[j] = static_cast<T>(input_buffer[i]);
channels[1].get()[j++] = input_buffer[i + 1]; channels[1].get()[j++] = static_cast<T>(input_buffer[i + 1]);
} }
} }
return channels; return channels;
} }
std::unique_ptr<SincResampler> CreateResampler(int src_samle_rate,
int dst_sample_rate,
size_t num_samples) {
const double io_ratio = static_cast<double>(src_samle_rate) /
static_cast<double>(dst_sample_rate);
auto resampler = std::make_unique<SincResampler>(io_ratio, num_samples);
resampler->PrimeWithSilence();
return resampler;
}
} // namespace } // namespace
namespace eng { namespace eng {
@ -79,52 +94,60 @@ bool Sound::Load(const std::string& file_name, bool stream) {
is_streaming_sound_ = stream; is_streaming_sound_ = stream;
LOG << (is_streaming_sound_ ? "Streaming " : "Loading ") << file_name << ". " LOG << (is_streaming_sound_ ? "Streaming " : "Loaded ") << file_name << ". "
<< mp3_dec_->samples << " samples, " << mp3_dec_->info.channels << mp3_dec_->samples << " samples, " << mp3_dec_->info.channels
<< " channels, " << mp3_dec_->info.hz << " hz, " << " channels, " << mp3_dec_->info.hz << " hz, "
<< "layer " << mp3_dec_->info.layer << ", " << "layer " << mp3_dec_->info.layer << ", "
<< "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps; << "avg_bitrate_kbps " << mp3_dec_->info.bitrate_kbps;
num_channels_ = mp3_dec_->info.channels; num_channels_ = mp3_dec_->info.channels;
hz_ = mp3_dec_->info.hz; sample_rate_ = mp3_dec_->info.hz;
num_samples_back_ = cur_sample_back_ = 0; num_samples_back_ = cur_sample_back_ = 0;
eof_ = false; eof_ = false;
DCHECK(num_channels_ > 0 && num_channels_ <= 2); DCHECK(num_channels_ > 0 && num_channels_ <= 2);
size_t system_hz = Engine::Get().GetAudioSampleRate(); hw_sample_rate_ = Engine::Get().GetAudioHardwareSampleRate();
if (is_streaming_sound_) { if (is_streaming_sound_) {
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz, if (sample_rate_ != hw_sample_rate_) {
kMaxSamplesPerChunk); for (int i = 0; i < mp3_dec_->info.channels; ++i) {
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate_,
(int)kMaxSamplesPerChunk / mp3_dec_->info.channels);
}
}
// Fill up buffers. // Fill up buffers.
StreamInternal(kMaxSamplesPerChunk, false); StreamInternal(kMaxSamplesPerChunk, false);
SwapBuffers(); SwapBuffers();
StreamInternal(kMaxSamplesPerChunk, false); StreamInternal(kMaxSamplesPerChunk, false);
if (eof_) { // No need to stream if sample fits into the buffer.
// Sample is smaller than buffer. No need to stream. if (eof_)
is_streaming_sound_ = false; is_streaming_sound_ = false;
mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset();
encoded_data_.reset();
resampler_.reset();
}
} else { } else {
resampler_ = std::make_unique<r8b::CDSPResampler16>(hz_, system_hz, if (sample_rate_ != hw_sample_rate_) {
mp3_dec_->samples); for (int i = 0; i < mp3_dec_->info.channels; ++i) {
resampler_[i] =
CreateResampler(sample_rate_, hw_sample_rate_,
mp3_dec_->samples / mp3_dec_->info.channels);
}
}
// Decode entire file. // Decode entire file.
StreamInternal(mp3_dec_->samples, false); StreamInternal(mp3_dec_->samples, false);
SwapBuffers(); SwapBuffers();
eof_ = true; eof_ = true;
}
// We are done with decoding for non-streaming sound. if (!is_streaming_sound_) {
// We are done with decoding.
encoded_data_.reset();
for (int i = 0; i < mp3_dec_->info.channels; ++i)
resampler_[i].reset();
mp3dec_ex_close(mp3_dec_.get()); mp3dec_ex_close(mp3_dec_.get());
mp3_dec_.reset(); mp3_dec_.reset();
encoded_data_.reset();
resampler_.reset();
} }
return true; return true;
@ -161,6 +184,7 @@ float* Sound::GetBuffer(int channel) const {
bool Sound::StreamInternal(size_t num_samples, bool loop) { bool Sound::StreamInternal(size_t num_samples, bool loop) {
auto buffer = std::make_unique<float[]>(num_samples); auto buffer = std::make_unique<float[]>(num_samples);
size_t samples_read_per_channel = 0;
cur_sample_back_ = mp3_dec_->cur_sample; cur_sample_back_ = mp3_dec_->cur_sample;
@ -173,8 +197,8 @@ bool Sound::StreamInternal(size_t num_samples, bool loop) {
return false; return false;
} }
num_samples_back_ = samples_read / mp3_dec_->info.channels; samples_read_per_channel = samples_read / mp3_dec_->info.channels;
if (!num_samples_back_ && loop) { if (!samples_read_per_channel && loop) {
mp3dec_ex_seek(mp3_dec_.get(), 0); mp3dec_ex_seek(mp3_dec_.get(), 0);
loop = false; loop = false;
continue; continue;
@ -182,47 +206,50 @@ bool Sound::StreamInternal(size_t num_samples, bool loop) {
break; break;
} }
if (num_samples_back_) if (samples_read_per_channel) {
Preprocess(std::move(buffer)); Preprocess(std::move(buffer), samples_read_per_channel);
else } else {
num_samples_back_ = 0;
eof_ = true; eof_ = true;
}
return true; return true;
} }
void Sound::Preprocess(std::unique_ptr<float[]> input_buffer) { void Sound::Preprocess(std::unique_ptr<float[]> input_buffer,
size_t system_hz = Engine::Get().GetAudioSampleRate(); size_t samples_per_channel) {
auto channels = Deinterleave<float>(num_channels_, samples_per_channel,
if (system_hz == hz_) {
auto channels = Deinterleave<float>(num_channels_, num_samples_back_,
input_buffer.get()); input_buffer.get());
if (hw_sample_rate_ == sample_rate_) {
// No need for resmapling. // No need for resmapling.
back_buffer_[0] = std::move(channels[0]); back_buffer_[0] = std::move(channels[0]);
if (num_channels_ == 2) if (num_channels_ == 2)
back_buffer_[1] = std::move(channels[1]); back_buffer_[1] = std::move(channels[1]);
num_samples_back_ = samples_per_channel;
} else { } else {
// r8b resampler supports only double floating point type. size_t num_resampled_samples = resampler_[0]->ChunkSize();
auto channels = Deinterleave<double>(num_channels_, num_samples_back_, DCHECK(num_resampled_samples ==
input_buffer.get()); (int)(((float)hw_sample_rate_ / (float)sample_rate_) *
samples_per_channel));
size_t resampled_num_samples =
((float)system_hz / (float)hz_) * num_samples_back_;
if (!back_buffer_[0]) { if (!back_buffer_[0]) {
if (max_samples_ < resampled_num_samples) if (max_samples_ < num_resampled_samples)
max_samples_ = resampled_num_samples; max_samples_ = num_resampled_samples;
back_buffer_[0] = std::make_unique<float[]>(max_samples_); back_buffer_[0] = std::make_unique<float[]>(max_samples_);
if (num_channels_ == 2) if (num_channels_ == 2)
back_buffer_[1] = std::make_unique<float[]>(max_samples_); back_buffer_[1] = std::make_unique<float[]>(max_samples_);
} }
num_samples_back_ = num_resampled_samples;
// Resample to match the system sample rate. // Resample to match the system sample rate.
for (int i = 0; i < num_channels_; ++i) { for (int i = 0; i < num_channels_; ++i) {
resampler_->oneshot(channels[i].get(), num_samples_back_, resampler_[i]->Resample(num_resampled_samples, back_buffer_[i].get(),
back_buffer_[i].get(), resampled_num_samples); [&](int frames, float* destination) {
memcpy(destination, channels[i].get(),
frames * sizeof(float));
});
} }
num_samples_back_ = resampled_num_samples;
} }
} }

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