mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-29 02:23:22 +08:00
More C++ work
This commit is contained in:
parent
6f4820e054
commit
8c446dd0d6
@ -3934,14 +3934,14 @@
|
||||
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
|
||||
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/96/c3b9934061393fe09cc90ff24b8d",
|
||||
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/2b/5641b3b40846f74f232771ac0457",
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/55/e7/7493c35661e347a164ccc9a6e150",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f4/a8/5f874f2c8ee0de54649b3142c2c5",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/87/cd/e155776004a096cd1981e8c45539",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/44/90/8453cc086294eaf8bd15f4df8332",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e8/98/2363d625af50c24c6ef3d2e9c58d",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7a/fd/1b7ecd5d084ea0e52974e463b0be",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/e8/88/8fe7875aa34660db68e74f25051d",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/23/2d/8239623b2d8745b4feb4b380b12a",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c3/2f/03140734daf10cd7e46a29c5c820",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/26/7a/d23e25fdda6f6b1d9ed3b03040b6"
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/07/e7/d8f0add439e55e3cce5e5768c80f",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fd/72/faa94ff6532a95c121fcb5a4f788",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d2/d1/99514fbe084fb0480d75f92ecb2c",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/5d/f8c5b24579236bef5209d7089044",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/5f/ff/5d34815d90dd4cd36f2a6f587958",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/45/1e/cea9badaf52032adb40e6c3b5e21",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/5d/63/96c2bbbedc03bd23824d7354b07d",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/44/94/4fa92ae4a1e726fb0b37e626e107",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/66/fd/8bb36157e75f78caa5373d9def18",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/af/2d/7546ed3c987435a743442603c21b"
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2020-09-30 for Ballistica version 1.5.26 build 20190</em></h4>
|
||||
<h4><em>last updated on 2020-10-02 for Ballistica version 1.5.26 build 20194</em></h4>
|
||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||
<hr>
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/app_config.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void AppConfig::Init() { new AppConfig(); }
|
||||
|
||||
auto AppConfig::Entry::FloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::StringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::IntValue() const -> int {
|
||||
throw Exception("not an int entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::BoolValue() const -> bool {
|
||||
throw Exception("not a bool entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultFloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultStringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultIntValue() const -> int {
|
||||
throw Exception("not an int entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultBoolValue() const -> bool {
|
||||
throw Exception("not a bool entry");
|
||||
}
|
||||
|
||||
class AppConfig::StringEntry : public AppConfig::Entry {
|
||||
public:
|
||||
StringEntry() = default;
|
||||
StringEntry(const char* name, std::string default_value)
|
||||
: Entry(name), default_value_(std::move(default_value)) {}
|
||||
auto GetType() const -> Type override { return Type::kString; }
|
||||
auto Resolve() const -> std::string {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_.c_str());
|
||||
}
|
||||
auto StringValue() const -> std::string override { return Resolve(); }
|
||||
auto DefaultStringValue() const -> std::string override {
|
||||
return default_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string default_value_;
|
||||
};
|
||||
|
||||
class AppConfig::FloatEntry : public AppConfig::Entry {
|
||||
public:
|
||||
FloatEntry() = default;
|
||||
FloatEntry(const char* name, float default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kFloat; }
|
||||
auto Resolve() const -> float {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto FloatValue() const -> float override { return Resolve(); }
|
||||
auto DefaultFloatValue() const -> float override { return default_value_; }
|
||||
|
||||
private:
|
||||
float default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::IntEntry : public AppConfig::Entry {
|
||||
public:
|
||||
IntEntry() = default;
|
||||
IntEntry(const char* name, int default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kInt; }
|
||||
auto Resolve() const -> int {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto IntValue() const -> int override { return Resolve(); }
|
||||
auto DefaultIntValue() const -> int override { return default_value_; }
|
||||
|
||||
private:
|
||||
int default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::BoolEntry : public AppConfig::Entry {
|
||||
public:
|
||||
BoolEntry() = default;
|
||||
BoolEntry(const char* name, bool default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kBool; }
|
||||
auto Resolve() const -> bool {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto BoolValue() const -> bool override { return Resolve(); }
|
||||
auto DefaultBoolValue() const -> bool override { return default_value_; }
|
||||
|
||||
private:
|
||||
bool default_value_{};
|
||||
};
|
||||
|
||||
AppConfig::AppConfig() {
|
||||
// (We're a singleton).
|
||||
assert(g_app_config == nullptr);
|
||||
g_app_config = this;
|
||||
SetupEntries();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AppConfig::CompleteMap(const T& entry_map) {
|
||||
for (auto&& i : entry_map) {
|
||||
assert(entries_by_name_.find(i.second.name()) == entries_by_name_.end());
|
||||
assert(i.first < decltype(i.first)::kLast);
|
||||
entries_by_name_[i.second.name()] = &i.second;
|
||||
}
|
||||
|
||||
// Make sure all values have entries.
|
||||
#if BA_DEBUG_BUILD
|
||||
int last = static_cast<int>(decltype(entry_map.begin()->first)::kLast); // ew
|
||||
for (int j = 0; j < last; ++j) {
|
||||
auto i2 =
|
||||
entry_map.find(static_cast<decltype(entry_map.begin()->first)>(j));
|
||||
if (i2 == entry_map.end()) {
|
||||
throw Exception("Missing appconfig entry " + std::to_string(j));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppConfig::SetupEntries() {
|
||||
// Register all our typed entries.
|
||||
float_entries_[FloatID::kScreenGamma] = FloatEntry("Screen Gamma", 1.0F);
|
||||
float_entries_[FloatID::kScreenPixelScale] =
|
||||
FloatEntry("Screen Pixel Scale", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScale] =
|
||||
FloatEntry("Touch Controls Scale", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScaleMovement] =
|
||||
FloatEntry("Touch Controls Scale Movement", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScaleActions] =
|
||||
FloatEntry("Touch Controls Scale Actions", 1.0F);
|
||||
float_entries_[FloatID::kSoundVolume] = FloatEntry("Sound Volume", 1.0F);
|
||||
float_entries_[FloatID::kMusicVolume] = FloatEntry("Music Volume", 1.0F);
|
||||
|
||||
// Note: keep this synced with the defaults in MainActivity.java.
|
||||
float gvrrts_default = g_platform->IsRunningOnDaydream() ? 1.0F : 0.5F;
|
||||
float_entries_[FloatID::kGoogleVRRenderTargetScale] =
|
||||
FloatEntry("GVR Render Target Scale", gvrrts_default);
|
||||
|
||||
string_entries_[StringID::kResolutionAndroid] =
|
||||
StringEntry("Resolution (Android)", "Auto");
|
||||
string_entries_[StringID::kTouchActionControlType] =
|
||||
StringEntry("Touch Action Control Type", "buttons");
|
||||
string_entries_[StringID::kTouchMovementControlType] =
|
||||
StringEntry("Touch Movement Control Type", "swipe");
|
||||
string_entries_[StringID::kGraphicsQuality] =
|
||||
StringEntry("Graphics Quality", "Auto");
|
||||
string_entries_[StringID::kTextureQuality] =
|
||||
StringEntry("Texture Quality", "Auto");
|
||||
string_entries_[StringID::kVerticalSync] =
|
||||
StringEntry("Vertical Sync", "Auto");
|
||||
string_entries_[StringID::kVRHeadRelativeAudio] =
|
||||
StringEntry("VR Head Relative Audio", "Auto");
|
||||
string_entries_[StringID::kMacControllerSubsystem] =
|
||||
StringEntry("Mac Controller Subsystem", "Classic");
|
||||
string_entries_[StringID::kTelnetPassword] =
|
||||
StringEntry("Telnet Password", "changeme");
|
||||
|
||||
int_entries_[IntID::kPort] = IntEntry("Port", kDefaultPort);
|
||||
int_entries_[IntID::kTelnetPort] =
|
||||
IntEntry("Telnet Port", kDefaultTelnetPort);
|
||||
|
||||
bool_entries_[BoolID::kTouchControlsSwipeHidden] =
|
||||
BoolEntry("Touch Controls Swipe Hidden", false);
|
||||
bool_entries_[BoolID::kFullscreen] = BoolEntry("Fullscreen", false);
|
||||
bool_entries_[BoolID::kKickIdlePlayers] =
|
||||
BoolEntry("Kick Idle Players", false);
|
||||
bool_entries_[BoolID::kAlwaysUseInternalKeyboard] =
|
||||
BoolEntry("Always Use Internal Keyboard", false);
|
||||
bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false);
|
||||
bool_entries_[BoolID::kTVBorder] =
|
||||
BoolEntry("TV Border", g_platform->IsRunningOnTV());
|
||||
bool_entries_[BoolID::kKeyboardP2Enabled] =
|
||||
BoolEntry("Keyboard P2 Enabled", false);
|
||||
bool_entries_[BoolID::kEnablePackageMods] =
|
||||
BoolEntry("Enable Package Mods", false);
|
||||
bool_entries_[BoolID::kChatMuted] = BoolEntry("Chat Muted", false);
|
||||
bool_entries_[BoolID::kEnableRemoteApp] =
|
||||
BoolEntry("Enable Remote App", true);
|
||||
bool_entries_[BoolID::kEnableTelnet] = BoolEntry("Enable Telnet", true);
|
||||
bool_entries_[BoolID::kDisableCameraShake] =
|
||||
BoolEntry("Disable Camera Shake", false);
|
||||
bool_entries_[BoolID::kDisableCameraGyro] =
|
||||
BoolEntry("Disable Camera Gyro", false);
|
||||
|
||||
// Now add everything to our name map and make sure all is kosher.
|
||||
CompleteMap(float_entries_);
|
||||
CompleteMap(int_entries_);
|
||||
CompleteMap(string_entries_);
|
||||
CompleteMap(bool_entries_);
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(FloatID id) -> float {
|
||||
auto i = float_entries_.find(id);
|
||||
if (i == float_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(StringID id) -> std::string {
|
||||
auto i = string_entries_.find(id);
|
||||
if (i == string_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(BoolID id) -> bool {
|
||||
auto i = bool_entries_.find(id);
|
||||
if (i == bool_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(IntID id) -> int {
|
||||
auto i = int_entries_.find(id);
|
||||
if (i == int_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
@ -1,12 +0,0 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
AppGlobals::AppGlobals(int argc_in, char** argv_in)
|
||||
: argc{argc_in},
|
||||
argv{argv_in},
|
||||
main_thread_id{std::this_thread::get_id()} {}
|
||||
|
||||
} // namespace ballistica
|
||||
@ -10,7 +10,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/networking/master_server_config.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
@ -85,8 +84,6 @@ class AppGlobals {
|
||||
std::mutex real_time_mutex;
|
||||
std::mutex thread_name_map_mutex;
|
||||
std::map<std::thread::id, std::string> thread_name_map;
|
||||
std::string master_server_addr{BA_MASTER_SERVER_DEFAULT_ADDR};
|
||||
std::string master_server_fallback_addr{BA_MASTER_SERVER_FALLBACK_ADDR};
|
||||
#if BA_DEBUG_BUILD
|
||||
std::mutex object_list_mutex;
|
||||
Object* object_list_first{};
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
#if BA_HEADLESS_BUILD
|
||||
|
||||
#include "ballistica/app/headless_app.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// We could technically use the vanilla App class here since we're not
|
||||
// changing anything.
|
||||
HeadlessApp::HeadlessApp(Thread* thread) : App(thread) {
|
||||
// NewThreadTimer(10, true, NewLambdaRunnable([this] {
|
||||
// assert(g_app);
|
||||
// g_app->RunEvents();
|
||||
// }));
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_HEADLESS_BUILD
|
||||
@ -1,101 +0,0 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/stress_test.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
#include "ballistica/input/input.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void StressTest::Update() {
|
||||
assert(InMainThread());
|
||||
|
||||
// Handle a little misc stuff here.
|
||||
// If we're currently running stress-tests, update that stuff.
|
||||
if (stress_testing_ && g_input) {
|
||||
// Update our fake inputs to make our dudes run around.
|
||||
g_input->ProcessStressTesting(stress_test_player_count_);
|
||||
|
||||
// Every 10 seconds update our stress-test stats.
|
||||
millisecs_t t = GetRealTime();
|
||||
if (t - last_stress_test_update_time_ >= 10000) {
|
||||
if (stress_test_stats_file_ == nullptr) {
|
||||
assert(g_platform);
|
||||
std::string f_name =
|
||||
g_platform->GetUserPythonDirectory() + "/stress_test_stats.csv";
|
||||
stress_test_stats_file_ = g_platform->FOpen(f_name.c_str(), "wb");
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
fprintf(stress_test_stats_file_,
|
||||
"time,averageFps,nodes,models,collide_models,textures,sounds,"
|
||||
"pssMem,sharedDirtyMem,privateDirtyMem\n");
|
||||
fflush(stress_test_stats_file_);
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// On android, let the OS know we've added or removed a file
|
||||
// (limit to android or we'll get an unimplemented warning).
|
||||
g_platform->AndroidRefreshFile(f_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
// See how many frames we've rendered this past interval.
|
||||
int total_frames_rendered;
|
||||
if (g_graphics_server && g_graphics_server->renderer()) {
|
||||
total_frames_rendered =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
total_frames_rendered = last_total_frames_rendered_;
|
||||
}
|
||||
float avg =
|
||||
static_cast<float>(total_frames_rendered
|
||||
- last_total_frames_rendered_)
|
||||
/ (static_cast<float>(t - last_stress_test_update_time_) / 1000.0f);
|
||||
last_total_frames_rendered_ = total_frames_rendered;
|
||||
uint32_t model_count = 0;
|
||||
uint32_t collide_model_count = 0;
|
||||
uint32_t texture_count = 0;
|
||||
uint32_t sound_count = 0;
|
||||
uint32_t node_count = 0;
|
||||
if (g_media) {
|
||||
model_count = g_media->total_model_count();
|
||||
collide_model_count = g_media->total_collide_model_count();
|
||||
texture_count = g_media->total_texture_count();
|
||||
sound_count = g_media->total_sound_count();
|
||||
}
|
||||
assert(g_game);
|
||||
std::string mem_usage = g_platform->GetMemUsageInfo();
|
||||
fprintf(stress_test_stats_file_, "%d,%.1f,%d,%d,%d,%d,%d,%s\n",
|
||||
static_cast_check_fit<int>(GetRealTime()), avg, node_count,
|
||||
model_count, collide_model_count, texture_count, sound_count,
|
||||
mem_usage.c_str());
|
||||
fflush(stress_test_stats_file_);
|
||||
}
|
||||
last_stress_test_update_time_ = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StressTest::Set(bool enable, int player_count) {
|
||||
assert(InMainThread());
|
||||
bool was_stress_testing = stress_testing_;
|
||||
stress_testing_ = enable;
|
||||
stress_test_player_count_ = player_count;
|
||||
|
||||
// If we're turning on, reset our intervals and things.
|
||||
if (!was_stress_testing && stress_testing_) {
|
||||
// So our first sample is 1 interval from now.
|
||||
last_stress_test_update_time_ = GetRealTime();
|
||||
|
||||
// Reset our frames-rendered tally.
|
||||
if (g_graphics_server && g_graphics_server->renderer()) {
|
||||
last_total_frames_rendered_ =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
// Assume zero if there's no graphics yet.
|
||||
last_total_frames_rendered_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ballistica
|
||||
@ -1,110 +0,0 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
#if BA_VR_BUILD
|
||||
|
||||
#include "ballistica/app/vr_app.h"
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
VRApp::VRApp(Thread* thread) : App(thread) {}
|
||||
|
||||
void VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) {
|
||||
PushCall([this, state] {
|
||||
// Convert this to a full hands state, adding in some simple elbow
|
||||
// positioning of our own and left/right.
|
||||
VRHandsState s;
|
||||
s.l.tx = -0.2f;
|
||||
s.l.ty = -0.2f;
|
||||
s.l.tz = -0.3f;
|
||||
|
||||
// Hmm; for now lets always assign this as right hand even when its in
|
||||
// left-handed mode to keep things simple on the back-end. Can change later
|
||||
// if there's a downside to that.
|
||||
s.r.type = VRHandType::kDaydreamRemote;
|
||||
s.r.tx = 0.2f;
|
||||
s.r.ty = -0.2f;
|
||||
s.r.tz = -0.3f;
|
||||
s.r.yaw = state.r0;
|
||||
s.r.pitch = state.r1;
|
||||
s.r.roll = state.r2;
|
||||
VRSetHands(s);
|
||||
});
|
||||
}
|
||||
|
||||
void VRApp::VRSetDrawDimensions(int w, int h) {
|
||||
g_graphics_server->VideoResize(w, h);
|
||||
}
|
||||
|
||||
void VRApp::VRPreDraw() {
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
assert(InMainThread());
|
||||
if (FrameDef* frame_def = g_graphics_server->GetRenderFrameDef()) {
|
||||
// Note: this could be part of PreprocessRenderFrameDef but
|
||||
// the non-vr path needs it to be separate since preprocess doesn't
|
||||
// happen sometimes. Should probably clean that up.
|
||||
g_graphics_server->RunFrameDefMeshUpdates(frame_def);
|
||||
|
||||
// store this for the duration of this frame
|
||||
vr_render_frame_def_ = frame_def;
|
||||
g_graphics_server->PreprocessRenderFrameDef(frame_def);
|
||||
}
|
||||
}
|
||||
|
||||
void VRApp::VRPostDraw() {
|
||||
assert(InMainThread());
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
if (vr_render_frame_def_) {
|
||||
g_graphics_server->FinishRenderFrameDef(vr_render_frame_def_);
|
||||
vr_render_frame_def_ = nullptr;
|
||||
}
|
||||
RunRenderUpkeepCycle();
|
||||
}
|
||||
|
||||
void VRApp::VRSetHead(float tx, float ty, float tz, float yaw, float pitch,
|
||||
float roll) {
|
||||
assert(InMainThread());
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
if (renderer == nullptr) return;
|
||||
renderer->VRSetHead(tx, ty, tz, yaw, pitch, roll);
|
||||
}
|
||||
|
||||
void VRApp::VRSetHands(const VRHandsState& state) {
|
||||
assert(InMainThread());
|
||||
|
||||
// Pass this along to the renderer (in this same thread) for drawing
|
||||
// (so hands can be drawn at their absolute most up-to-date positions, etc).
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
if (renderer == nullptr) return;
|
||||
renderer->VRSetHands(state);
|
||||
|
||||
// ALSO ship it off to the game/ui thread to actually handle input from it.
|
||||
g_game->PushVRHandsState(state);
|
||||
}
|
||||
|
||||
void VRApp::VRDrawEye(int eye, float yaw, float pitch, float roll, float tan_l,
|
||||
float tan_r, float tan_b, float tan_t, float eye_x,
|
||||
float eye_y, float eye_z, int viewport_x,
|
||||
int viewport_y) {
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
assert(InMainThread());
|
||||
if (vr_render_frame_def_) {
|
||||
// set up VR eye stuff...
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
renderer->VRSetEye(eye, yaw, pitch, roll, tan_l, tan_r, tan_b, tan_t, eye_x,
|
||||
eye_y, eye_z, viewport_x, viewport_y);
|
||||
g_graphics_server->DrawRenderFrameDef(vr_render_frame_def_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_VR_BUILD
|
||||
@ -29,7 +29,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't change here.
|
||||
const int kAppBuildNumber = 20193;
|
||||
const int kAppBuildNumber = 20194;
|
||||
const char* kAppVersion = "1.5.26";
|
||||
const char* kBlessingHash = nullptr;
|
||||
|
||||
|
||||
199
src/ballistica/game/account.cc
Normal file
199
src/ballistica/game/account.cc
Normal file
@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/account.h"
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto Account::AccountTypeFromString(const std::string& val) -> AccountType {
|
||||
if (val == "Game Center") {
|
||||
return AccountType::kGameCenter;
|
||||
} else if (val == "Game Circle") {
|
||||
return AccountType::kGameCircle;
|
||||
} else if (val == "Google Play") {
|
||||
return AccountType::kGooglePlay;
|
||||
} else if (val == "Steam") {
|
||||
return AccountType::kSteam;
|
||||
} else if (val == "Oculus") {
|
||||
return AccountType::kOculus;
|
||||
} else if (val == "NVIDIA China") {
|
||||
return AccountType::kNvidiaChina;
|
||||
} else if (val == "Test") {
|
||||
return AccountType::kTest;
|
||||
} else if (val == "Local") {
|
||||
return AccountType::kDevice;
|
||||
} else if (val == "Server") {
|
||||
return AccountType::kServer;
|
||||
} else {
|
||||
return AccountType::kInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::AccountTypeToString(AccountType type) -> std::string {
|
||||
switch (type) {
|
||||
case AccountType::kGameCenter:
|
||||
return "Game Center";
|
||||
case AccountType::kGameCircle:
|
||||
return "Game Circle";
|
||||
case AccountType::kGooglePlay:
|
||||
return "Google Play";
|
||||
case AccountType::kSteam:
|
||||
return "Steam";
|
||||
case AccountType::kOculus:
|
||||
return "Oculus";
|
||||
case AccountType::kTest:
|
||||
return "Test";
|
||||
case AccountType::kDevice:
|
||||
return "Local";
|
||||
case AccountType::kServer:
|
||||
return "Server";
|
||||
case AccountType::kNvidiaChina:
|
||||
return "NVIDIA China";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::AccountTypeToIconString(AccountType type) -> std::string {
|
||||
switch (type) {
|
||||
case AccountType::kTest:
|
||||
return g_game->CharStr(SpecialChar::kTestAccount);
|
||||
case AccountType::kNvidiaChina:
|
||||
return g_game->CharStr(SpecialChar::kNvidiaLogo);
|
||||
case AccountType::kGooglePlay:
|
||||
return g_game->CharStr(SpecialChar::kGooglePlayGamesLogo);
|
||||
case AccountType::kSteam:
|
||||
return g_game->CharStr(SpecialChar::kSteamLogo);
|
||||
case AccountType::kOculus:
|
||||
return g_game->CharStr(SpecialChar::kOculusLogo);
|
||||
case AccountType::kGameCenter:
|
||||
return g_game->CharStr(SpecialChar::kGameCenterLogo);
|
||||
case AccountType::kGameCircle:
|
||||
return g_game->CharStr(SpecialChar::kGameCircleLogo);
|
||||
case AccountType::kDevice:
|
||||
case AccountType::kServer:
|
||||
return g_game->CharStr(SpecialChar::kLocalAccount);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Account::Account() = default;
|
||||
|
||||
auto Account::GetAccountName() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return account_name_;
|
||||
}
|
||||
|
||||
auto Account::GetAccountID() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return account_id_;
|
||||
}
|
||||
|
||||
auto Account::GetAccountToken() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return account_token_;
|
||||
}
|
||||
|
||||
auto Account::GetAccountExtra() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return account_extra_;
|
||||
}
|
||||
|
||||
auto Account::GetAccountExtra2() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return account_extra_2_;
|
||||
}
|
||||
|
||||
auto Account::GetAccountState(int* state_num) -> AccountState {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_num) {
|
||||
*state_num = account_state_num_;
|
||||
}
|
||||
return account_state_;
|
||||
}
|
||||
|
||||
void Account::SetAccountExtra(const std::string& extra) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
account_extra_ = extra;
|
||||
}
|
||||
|
||||
void Account::SetAccountExtra2(const std::string& extra) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
account_extra_2_ = extra;
|
||||
}
|
||||
|
||||
void Account::SetAccountToken(const std::string& account_id,
|
||||
const std::string& token) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Hmm does this compare logic belong in here?
|
||||
if (account_id_ == account_id) {
|
||||
account_token_ = token;
|
||||
}
|
||||
}
|
||||
|
||||
void Account::SetAccount(AccountType account_type, AccountState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) {
|
||||
bool call_account_changed = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// We call out to python so need to be in game thread.
|
||||
assert(InGameThread());
|
||||
if (account_state_ != account_state
|
||||
|| g_app_globals->account_type != account_type
|
||||
|| account_id_ != account_id || account_name_ != account_name) {
|
||||
// Special case: if they sent a sign-out for an account type that is.
|
||||
// currently not signed in, ignore it.
|
||||
if (account_state == AccountState::kSignedOut
|
||||
&& (account_type != g_app_globals->account_type)) {
|
||||
// No-op.
|
||||
} else {
|
||||
account_state_ = account_state;
|
||||
g_app_globals->account_type = account_type;
|
||||
account_id_ = account_id;
|
||||
account_name_ = Utils::GetValidUTF8(account_name.c_str(), "gthm");
|
||||
|
||||
// If they signed out of an account, account type switches to invalid.
|
||||
if (account_state == AccountState::kSignedOut) {
|
||||
g_app_globals->account_type = AccountType::kInvalid;
|
||||
}
|
||||
account_state_num_ += 1;
|
||||
call_account_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call_account_changed) {
|
||||
// Inform python layer this has changed.
|
||||
g_python->AccountChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Account::SetProductsPurchased(const std::vector<std::string>& products) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::map<std::string, bool> purchases_old = product_purchases_;
|
||||
product_purchases_.clear();
|
||||
for (auto&& i : products) {
|
||||
product_purchases_[i] = true;
|
||||
}
|
||||
if (product_purchases_ != purchases_old) {
|
||||
product_purchases_state_++;
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::GetProductPurchased(const std::string& product) -> bool {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto i = product_purchases_.find(product);
|
||||
if (i == product_purchases_.end()) {
|
||||
return false;
|
||||
} else {
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
64
src/ballistica/game/account.h
Normal file
64
src/ballistica/game/account.h
Normal file
@ -0,0 +1,64 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_ACCOUNT_H_
|
||||
#define BALLISTICA_GAME_ACCOUNT_H_
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Global account functionality.
|
||||
class Account {
|
||||
public:
|
||||
Account();
|
||||
static auto AccountTypeFromString(const std::string& val) -> AccountType;
|
||||
static auto AccountTypeToString(AccountType type) -> std::string;
|
||||
static auto AccountTypeToIconString(AccountType type) -> std::string;
|
||||
|
||||
auto GetAccountName() -> std::string;
|
||||
auto GetAccountID() -> std::string;
|
||||
auto GetAccountToken() -> std::string;
|
||||
auto GetAccountExtra() -> std::string;
|
||||
auto GetAccountExtra2() -> std::string;
|
||||
|
||||
// Return the current account state.
|
||||
// If an int pointer is passed, state-num will also be returned.
|
||||
auto GetAccountState(int* state_num = nullptr) -> AccountState;
|
||||
|
||||
// An extra value included when passing our account info to the server
|
||||
// ..(can be used for platform-specific install-signature stuff, etc).
|
||||
void SetAccountExtra(const std::string& extra);
|
||||
void SetAccountExtra2(const std::string& extra);
|
||||
void SetAccountToken(const std::string& account_id, const std::string& token);
|
||||
|
||||
void SetAccount(AccountType account_type, AccountState account_state,
|
||||
const std::string& name, const std::string& id);
|
||||
|
||||
void SetProductsPurchased(const std::vector<std::string>& products);
|
||||
auto GetProductPurchased(const std::string& product) -> bool;
|
||||
auto product_purchases_state() const -> int {
|
||||
return product_purchases_state_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Protects all access to this account (we're thread-safe).
|
||||
std::mutex mutex_;
|
||||
std::map<std::string, bool> product_purchases_;
|
||||
int product_purchases_state_{};
|
||||
std::string account_name_;
|
||||
std::string account_id_;
|
||||
std::string account_token_;
|
||||
std::string account_extra_;
|
||||
std::string account_extra_2_;
|
||||
AccountState account_state_{AccountState::kSignedOut};
|
||||
int account_state_num_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_ACCOUNT_H_
|
||||
22
src/ballistica/game/client_controller_interface.h
Normal file
22
src/ballistica/game/client_controller_interface.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_CLIENT_CONTROLLER_INTERFACE_H_
|
||||
#define BALLISTICA_GAME_CLIENT_CONTROLLER_INTERFACE_H_
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// An interface for something that can control client-connections.
|
||||
// (such as an output-stream or a replay-client-session)
|
||||
// objects can register themselves as the current client-connection-controller
|
||||
// and then they will get control of all existing (and forthcoming) clients
|
||||
class ClientControllerInterface {
|
||||
public:
|
||||
virtual void OnClientConnected(ConnectionToClient* c) = 0;
|
||||
virtual void OnClientDisconnected(ConnectionToClient* c) = 0;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_CLIENT_CONTROLLER_INTERFACE_H_
|
||||
30
src/ballistica/game/friend_score_set.h
Normal file
30
src/ballistica/game/friend_score_set.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_FRIEND_SCORE_SET_H_
|
||||
#define BALLISTICA_GAME_FRIEND_SCORE_SET_H_
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Used by game-center/etc when reporting friend scores to the game.
|
||||
struct FriendScoreSet {
|
||||
FriendScoreSet(bool success, void* user_data)
|
||||
: success(success), user_data(user_data) {}
|
||||
struct Entry {
|
||||
Entry(int score, std::string name, bool is_me)
|
||||
: score(score), name(std::move(name)), is_me(is_me) {}
|
||||
int score;
|
||||
std::string name;
|
||||
bool is_me;
|
||||
};
|
||||
std::list<Entry> entries;
|
||||
bool success;
|
||||
void* user_data;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_FRIEND_SCORE_SET_H_
|
||||
3165
src/ballistica/game/game.cc
Normal file
3165
src/ballistica/game/game.cc
Normal file
File diff suppressed because it is too large
Load Diff
464
src/ballistica/game/game.h
Normal file
464
src/ballistica/game/game.h
Normal file
@ -0,0 +1,464 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_GAME_H_
|
||||
#define BALLISTICA_GAME_GAME_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/module.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
const int kMaxPartyNameCombinedSize = 25;
|
||||
|
||||
/// The Game Module generally runs on a dedicated thread; it manages
|
||||
/// all game logic, builds frame_defs to send to the graphics-server for
|
||||
/// rendering, etc.
|
||||
class Game : public Module {
|
||||
public:
|
||||
explicit Game(Thread* thread);
|
||||
~Game() override;
|
||||
auto LaunchHostSession(PyObject* session_type_obj,
|
||||
BenchmarkType benchmark_type = BenchmarkType::kNone)
|
||||
-> void;
|
||||
auto LaunchClientSession() -> void;
|
||||
auto LaunchReplaySession(const std::string& file_name) -> void;
|
||||
|
||||
auto PushSetAccountCall(AccountType account_type, AccountState account_state,
|
||||
const std::string& account_name,
|
||||
const std::string& account_id) -> void;
|
||||
auto PushSetAccountTokenCall(const std::string& account_id,
|
||||
const std::string& token) -> void;
|
||||
auto PushAdViewCompleteCall(const std::string& purpose, bool actually_showed)
|
||||
-> void;
|
||||
auto PushAnalyticsCall(const std::string& type, int increment) -> void;
|
||||
auto PushAwardAdTicketsCall() -> void;
|
||||
auto PushAwardAdTournamentEntryCall() -> void;
|
||||
auto PushPurchaseTransactionCall(const std::string& item,
|
||||
const std::string& receipt,
|
||||
const std::string& signature,
|
||||
const std::string& order_id,
|
||||
bool user_initiated) -> void;
|
||||
auto PushUDPConnectionPacketCall(const std::vector<uint8_t>& data,
|
||||
const SockAddr& addr) -> void;
|
||||
auto PushPartyInviteCall(const std::string& name,
|
||||
const std::string& invite_id) -> void;
|
||||
auto PushPartyInviteRevokeCall(const std::string& invite_id) -> void;
|
||||
auto PushInitialScreenCreatedCall() -> void;
|
||||
auto PushApplyConfigCall() -> void;
|
||||
auto PushRemoveGraphicsServerRenderHoldCall() -> void;
|
||||
auto PushInterruptSignalCall() -> void;
|
||||
|
||||
/// Push a generic 'menu press' event, optionally associated with an
|
||||
/// input device (nullptr to specify none). Note: caller must ensure
|
||||
/// a RemoveInputDevice() call does not arrive at the game thread
|
||||
/// before this one.
|
||||
auto PushMainMenuPressCall(InputDevice* device) -> void;
|
||||
|
||||
/// Notify the game of a screen-size change (used by the graphics server).
|
||||
auto PushScreenResizeCall(float virtual_width, float virtual_height,
|
||||
float physical_width, float physical_height)
|
||||
-> void;
|
||||
|
||||
auto PushGameServiceAchievementListCall(
|
||||
const std::set<std::string>& achievements) -> void;
|
||||
auto PushScoresToBeatResponseCall(bool success,
|
||||
const std::list<ScoreToBeat>& scores,
|
||||
void* py_callback) -> void;
|
||||
auto PushToggleCollisionGeometryDisplayCall() -> void;
|
||||
auto PushToggleDebugInfoDisplayCall() -> void;
|
||||
auto PushToggleManualCameraCall() -> void;
|
||||
auto PushHavePendingLoadsDoneCall() -> void;
|
||||
auto PushFreeMediaComponentRefsCall(
|
||||
const std::vector<Object::Ref<MediaComponentData>*>& components) -> void;
|
||||
auto PushSetFriendListCall(const std::vector<std::string>& friends) -> void;
|
||||
auto PushHavePendingLoadsCall() -> void;
|
||||
auto PushShutdownCall(bool soft) -> void;
|
||||
|
||||
auto PushInGameConsoleScriptCommand(const std::string& command) -> void;
|
||||
auto ToggleConsole() -> void;
|
||||
auto PushConsolePrintCall(const std::string& msg) -> void;
|
||||
auto PushStdinScriptCommand(const std::string& command) -> void;
|
||||
auto PushMediaPruneCall(int level) -> void;
|
||||
auto PushAskUserForTelnetAccessCall() -> void;
|
||||
|
||||
// Push Python call and keep it alive; must be called from game thread.
|
||||
auto PushPythonCall(const Object::Ref<PythonContextCall>& call) -> void;
|
||||
auto PushPythonCallArgs(const Object::Ref<PythonContextCall>& call,
|
||||
const PythonRef& args) -> void;
|
||||
|
||||
// Push Python call without keeping it alive; must be called from game thread.
|
||||
auto PushPythonWeakCall(const Object::WeakRef<PythonContextCall>& call)
|
||||
-> void;
|
||||
auto PushPythonWeakCallArgs(const Object::WeakRef<PythonContextCall>& call,
|
||||
const PythonRef& args) -> void;
|
||||
|
||||
// Push a raw Python call, decrements its refcount after running.
|
||||
// Can be pushed from any thread.
|
||||
auto PushPythonRawCallable(PyObject* callable) -> void;
|
||||
auto PushScreenMessage(const std::string& message, const Vector3f& color)
|
||||
-> void;
|
||||
auto RemovePlayer(Player* player) -> void;
|
||||
auto PushPlaySoundCall(SystemSoundID sound) -> void;
|
||||
auto PushConfirmQuitCall() -> void;
|
||||
auto PushStringEditSetCall(const std::string& value) -> void;
|
||||
auto PushStringEditCancelCall() -> void;
|
||||
auto PushFriendScoreSetCall(const FriendScoreSet& score_set) -> void;
|
||||
auto PushShowURLCall(const std::string& url) -> void;
|
||||
auto PushBackButtonCall(InputDevice* input_device) -> void;
|
||||
auto PushOnAppResumeCall() -> void;
|
||||
auto PushFrameDefRequest() -> void;
|
||||
auto PushDisconnectFromHostCall() -> void;
|
||||
auto PushClientDisconnectedCall(int id) -> void;
|
||||
auto PushHostConnectedUDPCall(const SockAddr& addr,
|
||||
bool print_connect_progress) -> void;
|
||||
auto PushDisconnectedFromHostCall() -> void;
|
||||
auto ChangeGameSpeed(int offs) -> void;
|
||||
auto ResetInput() -> void;
|
||||
auto RunMainMenu() -> void;
|
||||
auto HandleThreadPause() -> void override;
|
||||
|
||||
#if BA_GOOGLE_BUILD
|
||||
auto PushClientDisconnectedGooglePlayCall(int id) -> void;
|
||||
int GetGooglePlayClientCount() const;
|
||||
auto PushHostConnectedGooglePlayCall() -> void;
|
||||
auto PushClientConnectedGooglePlayCall(int id) -> void;
|
||||
auto PushCompressedGamePacketFromHostGooglePlayCall(
|
||||
const std::vector<uint8_t>& data) -> void;
|
||||
auto PushCompressedGamePacketFromClientGooglePlayCall(
|
||||
int google_client_id, const std::vector<uint8_t>& data) -> void;
|
||||
#endif
|
||||
|
||||
#if BA_VR_BUILD
|
||||
auto PushVRHandsState(const VRHandsState& state) -> void;
|
||||
const VRHandsState& vr_hands_state() const { return vr_hands_state_; }
|
||||
#endif
|
||||
|
||||
// Resets tracking used to detect cheating and tampering in local tournaments.
|
||||
auto ResetActivityTracking() -> void;
|
||||
|
||||
// Return whichever context is front and center.
|
||||
auto GetForegroundContext() -> Context;
|
||||
|
||||
// Return whichever session is front and center.
|
||||
auto GetForegroundSession() const -> Session* {
|
||||
return foreground_session_.get();
|
||||
}
|
||||
|
||||
auto NewRealTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
auto DeleteRealTimer(int timer_id) -> void;
|
||||
auto SetRealTimerLength(int timer_id, millisecs_t length) -> void;
|
||||
auto SetLanguageKeys(const std::map<std::string, std::string>& language)
|
||||
-> void;
|
||||
auto GetResourceString(const std::string& key) -> std::string;
|
||||
auto CharStr(SpecialChar id) -> std::string;
|
||||
auto CompileResourceString(const std::string& s, const std::string& loc,
|
||||
bool* valid = nullptr) -> std::string;
|
||||
auto kick_idle_players() const -> bool { return kick_idle_players_; }
|
||||
auto IsInUIContext() const -> bool;
|
||||
|
||||
// Return the actual UI context (hmm couldn't we just use g_ui?).
|
||||
auto GetUIContextTarget() const -> UI* {
|
||||
assert(g_ui);
|
||||
return g_ui;
|
||||
}
|
||||
|
||||
// Simply return a context-state pointing to the ui-context (so you don't have
|
||||
// to include the ui header).
|
||||
auto GetUIContext() const -> Context;
|
||||
|
||||
// Returns the base time used to drive local sims/etc. This generally tries
|
||||
// to match real-time but has a bit of leeway to sync up with frame drawing or
|
||||
// slow down if things are behind (it tries to progress by exactly 1000/60 ms
|
||||
// each frame, provided we're rendering 60hz).
|
||||
auto master_time() const -> millisecs_t { return master_time_; }
|
||||
|
||||
auto debug_speed_mult() const -> float { return debug_speed_mult_; }
|
||||
auto SetDebugSpeedExponent(int val) -> void;
|
||||
|
||||
auto SetReplaySpeedExponent(int val) -> void;
|
||||
auto replay_speed_exponent() const -> int { return replay_speed_exponent_; }
|
||||
auto replay_speed_mult() const -> float { return replay_speed_mult_; }
|
||||
|
||||
// Returns our host-connection or nullptr if there is none.
|
||||
auto connection_to_host() -> ConnectionToHost* {
|
||||
return connection_to_host_.get();
|
||||
}
|
||||
auto GetConnectionToHostUDP() -> ConnectionToHostUDP*;
|
||||
|
||||
// Send a screen message to all connected clients AND print it on the host.
|
||||
auto SendScreenMessageToAll(const std::string& s, float r, float g, float b)
|
||||
-> void;
|
||||
|
||||
// send a screen message to all connected clients
|
||||
auto SendScreenMessageToClients(const std::string& s, float r, float g,
|
||||
float b) -> void;
|
||||
|
||||
// Send a screen message to specific connected clients (those matching the IDs
|
||||
// specified) the id -1 can be used to specify the host.
|
||||
auto SendScreenMessageToSpecificClients(const std::string& s, float r,
|
||||
float g, float b,
|
||||
const std::vector<int>& clients)
|
||||
-> void;
|
||||
|
||||
// Return our client connections (if any).
|
||||
// FIXME: this prunes invalid connections, but it is necessary?
|
||||
// Can we just use connections_to_clients() for direct access?
|
||||
auto GetConnectionsToClients() -> std::vector<ConnectionToClient*>;
|
||||
|
||||
// Return the number of connections-to-client with "connected" status true.
|
||||
auto GetConnectedClientCount() const -> int;
|
||||
|
||||
auto GetPartySize() const -> int;
|
||||
auto last_connection_to_client_join_time() const -> millisecs_t {
|
||||
return last_connection_to_client_join_time_;
|
||||
}
|
||||
auto set_last_connection_to_client_join_time(millisecs_t val) -> void {
|
||||
last_connection_to_client_join_time_ = val;
|
||||
}
|
||||
|
||||
// Simple thread safe query.
|
||||
auto has_connection_to_host() const -> bool {
|
||||
return has_connection_to_host_;
|
||||
}
|
||||
|
||||
auto game_roster() const -> cJSON* { return game_roster_; }
|
||||
|
||||
auto SendChatMessage(const std::string& message,
|
||||
const std::vector<int>* clients = nullptr,
|
||||
const std::string* sender_override = nullptr) -> void;
|
||||
|
||||
// Quick test as to whether there are clients. Does not check if they are
|
||||
// fully connected.
|
||||
auto has_connection_to_clients() const -> bool {
|
||||
assert(InGameThread());
|
||||
return (!connections_to_clients_.empty());
|
||||
}
|
||||
|
||||
auto chat_messages() const -> const std::list<std::string>& {
|
||||
return chat_messages_;
|
||||
}
|
||||
|
||||
// Whoever wants to wrangle current client connections should call this
|
||||
// to register itself. Note that it must explicitly call unregister when
|
||||
// unregistering itself.
|
||||
auto RegisterClientController(ClientControllerInterface* c) -> void;
|
||||
auto UnregisterClientController(ClientControllerInterface* c) -> void;
|
||||
|
||||
// Used to know which globals is in control currently/etc.
|
||||
auto GetForegroundScene() const -> Scene* {
|
||||
assert(InGameThread());
|
||||
return foreground_scene_.get();
|
||||
}
|
||||
auto SetForegroundScene(Scene* sg) -> void;
|
||||
|
||||
// Returns true if disconnect attempts are supported.
|
||||
auto DisconnectClient(int client_id, int ban_seconds) -> bool;
|
||||
auto UpdateGameRoster() -> void;
|
||||
auto IsPlayerBanned(const PlayerSpec& spec) -> bool;
|
||||
auto BanPlayer(const PlayerSpec& spec, millisecs_t duration) -> void;
|
||||
|
||||
// For applying player-profiles data from the master-server.
|
||||
auto SetClientInfoFromMasterServer(const std::string& client_token,
|
||||
PyObject* info) -> void;
|
||||
auto GetPrintUDPConnectProgress() const -> bool {
|
||||
return print_udp_connect_progress_;
|
||||
}
|
||||
|
||||
// For cheat detection. Returns the largest amount of time that has passed
|
||||
// between frames since our last reset (for detecting memory modification
|
||||
// UIs/etc).
|
||||
auto largest_draw_time_increment() const -> millisecs_t {
|
||||
return largest_draw_time_increment_since_last_reset_;
|
||||
}
|
||||
|
||||
// Anti-hacker stuff.
|
||||
auto GetTotalTimeSinceReset() const -> millisecs_t {
|
||||
return last_draw_real_time_ - first_draw_real_time_;
|
||||
}
|
||||
auto SetForegroundSession(Session* s) -> void;
|
||||
auto SetGameRoster(cJSON* r) -> void;
|
||||
auto LocalDisplayChatMessage(const std::vector<uint8_t>& buffer) -> void;
|
||||
auto ShouldAnnouncePartyJoinsAndLeaves() -> bool;
|
||||
|
||||
auto SetAdCompletionCall(PyObject* obj, bool pass_actually_showed) -> void;
|
||||
auto CallAdCompletionCall(bool actually_showed) -> void;
|
||||
auto RunGeneralAdComplete(bool actually_watched) -> void;
|
||||
|
||||
auto StartKickVote(ConnectionToClient* starter, ConnectionToClient* target)
|
||||
-> void;
|
||||
auto require_client_authentication() const {
|
||||
return require_client_authentication_;
|
||||
}
|
||||
auto set_require_client_authentication(bool enable) -> void {
|
||||
require_client_authentication_ = enable;
|
||||
}
|
||||
auto set_kick_voting_enabled(bool enable) -> void {
|
||||
kick_voting_enabled_ = enable;
|
||||
}
|
||||
auto set_admin_public_ids(const std::set<std::string>& ids) -> void {
|
||||
admin_public_ids_ = ids;
|
||||
}
|
||||
const std::set<std::string>& admin_public_ids() const {
|
||||
return admin_public_ids_;
|
||||
}
|
||||
|
||||
auto connections_to_clients()
|
||||
-> const std::map<int, Object::Ref<ConnectionToClient> >& {
|
||||
return connections_to_clients_;
|
||||
}
|
||||
auto client_controller() -> ClientControllerInterface* {
|
||||
return client_controller_;
|
||||
}
|
||||
auto kick_vote_in_progress() const -> bool { return kick_vote_in_progress_; }
|
||||
|
||||
#if BA_GOOGLE_BUILD
|
||||
auto ClientIDFromGooglePlayClientID(int google_id) -> int;
|
||||
auto GooglePlayClientIDFromClientID(int client_id) -> int;
|
||||
#endif
|
||||
|
||||
auto SetPublicPartyEnabled(bool val) -> void;
|
||||
auto public_party_enabled() const { return public_party_enabled_; }
|
||||
auto public_party_size() const { return public_party_size_; }
|
||||
auto SetPublicPartySize(int count) -> void;
|
||||
auto public_party_max_size() const { return public_party_max_size_; }
|
||||
auto SetPublicPartyMaxSize(int count) -> void;
|
||||
auto SetPublicPartyName(const std::string& name) -> void;
|
||||
auto SetPublicPartyStatsURL(const std::string& name) -> void;
|
||||
auto public_party_name() const { return public_party_name_; }
|
||||
auto public_party_player_count() const { return public_party_player_count_; }
|
||||
auto SetPublicPartyPlayerCount(int count) -> void;
|
||||
auto ran_app_launch_commands() const { return ran_app_launch_commands_; }
|
||||
|
||||
private:
|
||||
auto InitSpecialChars() -> void;
|
||||
auto AdViewComplete(const std::string& purpose, bool actually_showed) -> void;
|
||||
auto Analytics(const std::string& type, int increment) -> void;
|
||||
auto AwardAdTickets() -> void;
|
||||
auto AwardAdTournamentEntry() -> void;
|
||||
auto Draw() -> void;
|
||||
auto PurchaseTransaction(const std::string& item, const std::string& receipt,
|
||||
const std::string& signature,
|
||||
const std::string& order_id, bool user_initiated)
|
||||
-> void;
|
||||
auto UDPConnectionPacket(const std::vector<uint8_t>& data,
|
||||
const SockAddr& addr) -> void;
|
||||
auto PartyInvite(const std::string& name, const std::string& invite_id)
|
||||
-> void;
|
||||
auto PartyInviteRevoke(const std::string& invite_id) -> void;
|
||||
auto InitialScreenCreated() -> void;
|
||||
auto MainMenuPress(InputDevice* device) -> void;
|
||||
auto ScreenResize(float virtual_width, float virtual_height,
|
||||
float pixel_width, float pixel_height) -> void;
|
||||
auto GameServiceAchievementList(const std::set<std::string>& achievements)
|
||||
-> void;
|
||||
auto ScoresToBeatResponse(bool success, const std::list<ScoreToBeat>& scores,
|
||||
void* py_callback) -> void;
|
||||
|
||||
#if BA_VR_BUILD
|
||||
VRHandsState vr_hands_state_;
|
||||
#endif
|
||||
#if BA_RIFT_BUILD
|
||||
int rift_step_index_{};
|
||||
#endif
|
||||
|
||||
auto HandleClientDisconnected(int id) -> void;
|
||||
auto ForceDisconnectClients() -> void;
|
||||
auto Prune() -> void; // Periodic pruning of dead stuff.
|
||||
auto Update() -> void;
|
||||
auto Process() -> void;
|
||||
auto UpdateKickVote() -> void;
|
||||
auto RunAppLaunchCommands() -> void;
|
||||
auto PruneSessions() -> void;
|
||||
auto ApplyConfig() -> void;
|
||||
auto UpdateProcessTimer() -> void;
|
||||
auto Reset() -> void;
|
||||
auto GetGameRosterMessage() -> std::vector<uint8_t>;
|
||||
auto CleanUpBeforeConnectingToHost() -> void;
|
||||
auto Shutdown(bool soft) -> void;
|
||||
auto PushPublicPartyState() -> void;
|
||||
|
||||
std::map<int, int> google_play_id_to_client_id_map_;
|
||||
std::map<int, int> client_id_to_google_play_id_map_;
|
||||
bool print_udp_connect_progress_{true};
|
||||
std::list<std::pair<millisecs_t, PlayerSpec> > banned_players_;
|
||||
ClientControllerInterface* client_controller_{};
|
||||
std::list<std::string> chat_messages_;
|
||||
|
||||
// Simple flag for thread-safe access.
|
||||
bool has_connection_to_host_{};
|
||||
|
||||
// Prevents us from printing multiple 'you got disconnected' messages.
|
||||
bool printed_host_disconnect_{};
|
||||
bool chat_muted_{};
|
||||
bool first_update_{true};
|
||||
bool game_roster_dirty_{};
|
||||
millisecs_t last_connection_to_client_join_time_{};
|
||||
int debug_speed_exponent_{};
|
||||
float debug_speed_mult_{1.0f};
|
||||
int replay_speed_exponent_{};
|
||||
float replay_speed_mult_{1.0f};
|
||||
bool have_sent_initial_frame_def_{};
|
||||
millisecs_t master_time_{};
|
||||
millisecs_t master_time_offset_{};
|
||||
millisecs_t last_session_update_master_time_{};
|
||||
millisecs_t last_game_roster_send_time_{};
|
||||
millisecs_t largest_draw_time_increment_since_last_reset_{};
|
||||
millisecs_t last_draw_real_time_{};
|
||||
millisecs_t first_draw_real_time_{};
|
||||
|
||||
// *All* existing sessions (including old ones waiting to shut down).
|
||||
std::vector<Object::Ref<Session> > sessions_;
|
||||
Object::WeakRef<Scene> foreground_scene_;
|
||||
Object::WeakRef<Session> foreground_session_;
|
||||
std::mutex language_mutex_;
|
||||
std::map<std::string, std::string> language_;
|
||||
std::mutex special_char_mutex_;
|
||||
std::map<SpecialChar, std::string> special_char_strings_;
|
||||
bool ran_app_launch_commands_{};
|
||||
bool kick_idle_players_{};
|
||||
std::unique_ptr<TimerList> realtimers_;
|
||||
Timer* process_timer_{};
|
||||
Timer* headless_update_timer_{};
|
||||
Timer* media_prune_timer_{};
|
||||
Timer* debug_timer_{};
|
||||
bool have_pending_loads_{};
|
||||
bool in_update_{};
|
||||
bool require_client_authentication_{};
|
||||
bool kick_voting_enabled_{true};
|
||||
std::set<std::string> admin_public_ids_;
|
||||
|
||||
// Try to minimize the chance a garbage packet will have this id.
|
||||
int next_connection_to_client_id_{113};
|
||||
std::map<int, Object::Ref<ConnectionToClient> > connections_to_clients_;
|
||||
Object::Ref<ConnectionToHost> connection_to_host_;
|
||||
cJSON* game_roster_{};
|
||||
millisecs_t kick_vote_end_time_{};
|
||||
bool kick_vote_in_progress_{};
|
||||
int last_kick_votes_needed_{-1};
|
||||
Object::WeakRef<ConnectionToClient> kick_vote_starter_;
|
||||
Object::WeakRef<ConnectionToClient> kick_vote_target_;
|
||||
Object::Ref<PythonContextCall> ad_completion_callback_;
|
||||
millisecs_t last_ad_start_time_{};
|
||||
bool ad_completion_callback_pass_actually_showed_{};
|
||||
bool public_party_enabled_{false};
|
||||
int public_party_size_{1}; // Always count ourself (is that what we want?).
|
||||
int public_party_max_size_{8};
|
||||
int public_party_player_count_{0};
|
||||
int public_party_max_player_count_{8};
|
||||
std::string public_party_name_;
|
||||
std::string public_party_min_league_;
|
||||
std::string public_party_stats_url_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_GAME_H_
|
||||
1242
src/ballistica/game/game_stream.cc
Normal file
1242
src/ballistica/game/game_stream.cc
Normal file
File diff suppressed because it is too large
Load Diff
159
src/ballistica/game/game_stream.h
Normal file
159
src/ballistica/game/game_stream.h
Normal file
@ -0,0 +1,159 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_GAME_STREAM_H_
|
||||
#define BALLISTICA_GAME_GAME_STREAM_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/game/client_controller_interface.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A mechanism for dumping a live session or session-creation-commands to a
|
||||
// stream of messages that can be saved to file or sent over the network.
|
||||
class GameStream : public Object, public ClientControllerInterface {
|
||||
public:
|
||||
GameStream(HostSession* host_session, bool saveReplay);
|
||||
~GameStream() override;
|
||||
void SetTime(millisecs_t t);
|
||||
void AddScene(Scene* s);
|
||||
void RemoveScene(Scene* s);
|
||||
void StepScene(Scene* s);
|
||||
void AddNode(Node* n);
|
||||
void NodeOnCreate(Node* n);
|
||||
void RemoveNode(Node* n);
|
||||
void SetForegroundScene(Scene* sg);
|
||||
void AddMaterial(Material* m);
|
||||
void RemoveMaterial(Material* m);
|
||||
void AddMaterialComponent(Material* m, MaterialComponent* c);
|
||||
void AddTexture(Texture* t);
|
||||
void RemoveTexture(Texture* t);
|
||||
void AddModel(Model* t);
|
||||
void RemoveModel(Model* t);
|
||||
void AddSound(Sound* t);
|
||||
void RemoveSound(Sound* t);
|
||||
void AddData(Data* d);
|
||||
void RemoveData(Data* d);
|
||||
void AddCollideModel(CollideModel* t);
|
||||
void RemoveCollideModel(CollideModel* t);
|
||||
void ConnectNodeAttribute(Node* src_node, NodeAttributeUnbound* src_attr,
|
||||
Node* dst_node, NodeAttributeUnbound* dst_attr);
|
||||
void NodeMessage(Node* node, const char* buffer, size_t size);
|
||||
void SetNodeAttr(const NodeAttribute& attr, float val);
|
||||
void SetNodeAttr(const NodeAttribute& attr, int64_t val);
|
||||
void SetNodeAttr(const NodeAttribute& attr, bool val);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::vector<float>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::vector<int64_t>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::string& val);
|
||||
void SetNodeAttr(const NodeAttribute& attr, Node* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::vector<Node*>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, Player* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr,
|
||||
const std::vector<Material*>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, Texture* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr,
|
||||
const std::vector<Texture*>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, Sound* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::vector<Sound*>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, Model* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr, const std::vector<Model*>& vals);
|
||||
void SetNodeAttr(const NodeAttribute& attr, CollideModel* n);
|
||||
void SetNodeAttr(const NodeAttribute& attr,
|
||||
const std::vector<CollideModel*>& vals);
|
||||
void PlaySoundAtPosition(Sound* sound, float volume, float x, float y,
|
||||
float z);
|
||||
void PlaySound(Sound* sound, float volume);
|
||||
void EmitBGDynamics(const BGDynamicsEmission& e);
|
||||
auto GetSoundID(Sound* s) -> int64_t;
|
||||
auto GetMaterialID(Material* m) -> int64_t;
|
||||
void ScreenMessageBottom(const std::string& val, float r, float g, float b);
|
||||
void ScreenMessageTop(const std::string& val, float r, float g, float b,
|
||||
Texture* texture, Texture* tint_texture, float tint_r,
|
||||
float tint_g, float tint_b, float tint2_r,
|
||||
float tint2_g, float tint2_b);
|
||||
void OnClientConnected(ConnectionToClient* c) override;
|
||||
void OnClientDisconnected(ConnectionToClient* c) override;
|
||||
auto GetOutMessage() const -> std::vector<uint8_t>;
|
||||
|
||||
private:
|
||||
HostSession* host_session_;
|
||||
|
||||
// Make sure the scene is in our stream.
|
||||
auto IsValidScene(Scene* val) -> bool;
|
||||
auto IsValidNode(Node* val) -> bool;
|
||||
auto IsValidTexture(Texture* val) -> bool;
|
||||
auto IsValidModel(Model* val) -> bool;
|
||||
auto IsValidSound(Sound* val) -> bool;
|
||||
auto IsValidData(Data* val) -> bool;
|
||||
auto IsValidCollideModel(CollideModel* val) -> bool;
|
||||
auto IsValidMaterial(Material* val) -> bool;
|
||||
millisecs_t next_flush_time_;
|
||||
void Flush();
|
||||
void AddMessageToReplay(const std::vector<uint8_t>& message);
|
||||
|
||||
// Individual command going into the commands-messages.
|
||||
std::vector<uint8_t> out_command_;
|
||||
|
||||
// The complete message full of commands.
|
||||
std::vector<uint8_t> out_message_;
|
||||
std::vector<ConnectionToClient*> connections_to_clients_;
|
||||
std::vector<ConnectionToClient*> connections_to_clients_ignored_;
|
||||
bool writing_replay_;
|
||||
void Fail();
|
||||
millisecs_t last_physics_correction_time_;
|
||||
millisecs_t last_send_time_;
|
||||
void ShipSessionCommandsMessage();
|
||||
void SendPhysicsCorrection(bool blend);
|
||||
void EndCommand(bool is_time_set = false);
|
||||
void WriteString(const std::string& s);
|
||||
void WriteFloat(float val);
|
||||
void WriteFloats(size_t count, const float* vals);
|
||||
void WriteInts32(size_t count, const int32_t* vals);
|
||||
void WriteInts64(size_t count, const int64_t* vals);
|
||||
void WriteChars(size_t count, const char* vals);
|
||||
void WriteCommand(SessionCommand cmd);
|
||||
void WriteCommandInt32(SessionCommand cmd, int32_t value);
|
||||
void WriteCommandInt64(SessionCommand cmd, int64_t value);
|
||||
void WriteCommandInt32_2(SessionCommand cmd, int32_t value1, int32_t value2);
|
||||
void WriteCommandInt64_2(SessionCommand cmd, int64_t value1, int64_t value2);
|
||||
void WriteCommandInt32_3(SessionCommand cmd, int32_t value1, int32_t value2,
|
||||
int32_t value3);
|
||||
void WriteCommandInt64_3(SessionCommand cmd, int64_t value1, int64_t value2,
|
||||
int64_t value3);
|
||||
void WriteCommandInt32_4(SessionCommand cmd, int32_t value1, int32_t value2,
|
||||
int32_t value3, int32_t value4);
|
||||
void WriteCommandInt64_4(SessionCommand cmd, int64_t value1, int64_t value2,
|
||||
int64_t value3, int64_t value4);
|
||||
template <typename T>
|
||||
auto GetPointerCount(const std::vector<T*>& vec) -> size_t;
|
||||
template <typename T>
|
||||
auto GetFreeIndex(std::vector<T*>* vec, std::vector<size_t>* free_indices)
|
||||
-> size_t;
|
||||
template <typename T>
|
||||
void Add(T* val, std::vector<T*>* vec, std::vector<size_t>* free_indices);
|
||||
template <typename T>
|
||||
void Remove(T* val, std::vector<T*>* vec, std::vector<size_t>* free_indices);
|
||||
millisecs_t time_;
|
||||
std::vector<Scene*> scenes_;
|
||||
std::vector<size_t> free_indices_scene_graphs_;
|
||||
std::vector<Node*> nodes_;
|
||||
std::vector<size_t> free_indices_nodes_;
|
||||
std::vector<Material*> materials_;
|
||||
std::vector<size_t> free_indices_materials_;
|
||||
std::vector<Texture*> textures_;
|
||||
std::vector<size_t> free_indices_textures_;
|
||||
std::vector<Model*> models_;
|
||||
std::vector<size_t> free_indices_models_;
|
||||
std::vector<Sound*> sounds_;
|
||||
std::vector<size_t> free_indices_sounds_;
|
||||
std::vector<Data*> datas_;
|
||||
std::vector<size_t> free_indices_datas_;
|
||||
std::vector<CollideModel*> collide_models_;
|
||||
std::vector<size_t> free_indices_collide_models_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_GAME_STREAM_H_
|
||||
528
src/ballistica/game/host_activity.cc
Normal file
528
src/ballistica/game/host_activity.cc
Normal file
@ -0,0 +1,528 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/host_activity.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ballistica/dynamics/material/material.h"
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/game/player.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/generic/lambda_runnable.h"
|
||||
#include "ballistica/generic/timer.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/media/component/collide_model.h"
|
||||
#include "ballistica/media/component/data.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
#include "ballistica/scene/node/globals_node.h"
|
||||
#include "ballistica/scene/node/node_type.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
HostActivity::HostActivity(HostSession* host_session) {
|
||||
// Store a link to the HostSession and add ourself to it.
|
||||
host_session_ = host_session;
|
||||
|
||||
// Create our game timer - gets called whenever game should step.
|
||||
step_scene_timer_ =
|
||||
base_timers_.NewTimer(base_time_, kGameStepMilliseconds, 0, -1,
|
||||
NewLambdaRunnable([this] { StepScene(); }));
|
||||
SetGameSpeed(1.0f);
|
||||
{
|
||||
ScopedSetContext cp(this); // So scene picks us up as context.
|
||||
scene_ = Object::New<Scene>(0);
|
||||
|
||||
// If there's an output stream, add to it.
|
||||
if (GameStream* out = host_session->GetGameStream()) {
|
||||
out->AddScene(scene_.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HostActivity::~HostActivity() {
|
||||
shutting_down_ = true;
|
||||
|
||||
// Put the scene in shut-down mode before we start killing stuff.
|
||||
// (this generates warnings, suppresses messages, etc)
|
||||
scene_->set_shutting_down(true);
|
||||
|
||||
// Clear out all python calls registered in our context.
|
||||
// (should wipe out refs to our activity and prevent them from running without
|
||||
// a valid activity context)
|
||||
for (auto&& i : python_calls_) {
|
||||
if (i.exists()) {
|
||||
i->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all our media dead to clear it out of our output-stream cleanly
|
||||
for (auto&& i : textures_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : collide_models_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : materials_) {
|
||||
if (i.exists()) {
|
||||
i->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear our timers and scene; this should wipe out any remaining refs to our
|
||||
// python activity, allowing it to die.
|
||||
base_timers_.Clear();
|
||||
sim_timers_.Clear();
|
||||
scene_.Clear();
|
||||
|
||||
// Report outstanding calls. There shouldn't be any at this point. Actually it
|
||||
// turns out there's generally 1; whichever call was responsible for killing
|
||||
// this activity will still be in progress.. so let's report on 2 or more I
|
||||
// guess.
|
||||
#if BA_DEBUG_BUILD
|
||||
PruneDeadRefs(&python_calls_);
|
||||
if (python_calls_.size() > 1) {
|
||||
std::string s = "WARNING: " + std::to_string(python_calls_.size())
|
||||
+ " live PythonContextCalls at shutdown for "
|
||||
+ "HostActivity" + " (1 call is expected):";
|
||||
int count = 1;
|
||||
for (auto& python_call : python_calls_)
|
||||
s += "\n " + std::to_string(count++) + ": "
|
||||
+ (*python_call).GetObjectDescription();
|
||||
Log(s);
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
}
|
||||
|
||||
auto HostActivity::GetGameStream() const -> GameStream* {
|
||||
if (!host_session_.exists()) return nullptr;
|
||||
return host_session_->GetGameStream();
|
||||
}
|
||||
|
||||
void HostActivity::StepScene() {
|
||||
int cycle_count = 1;
|
||||
if (host_session_->benchmark_type() == BenchmarkType::kCPU) {
|
||||
cycle_count = 100;
|
||||
}
|
||||
|
||||
for (int cycle = 0; cycle < cycle_count; ++cycle) {
|
||||
assert(InGameThread());
|
||||
|
||||
// Clear our player-positions for this step.
|
||||
// FIXME: Move this to scene and/or player node.
|
||||
assert(host_session_.exists());
|
||||
for (auto&& player : host_session_->players()) {
|
||||
assert(player.exists());
|
||||
player->set_have_position(false);
|
||||
}
|
||||
|
||||
// Run our sim-time timers.
|
||||
sim_timers_.Run(scene()->time());
|
||||
|
||||
// Send die-messages/etc to out-of-bounds stuff.
|
||||
HandleOutOfBoundsNodes();
|
||||
|
||||
scene()->Step();
|
||||
}
|
||||
}
|
||||
|
||||
void HostActivity::RegisterCall(PythonContextCall* call) {
|
||||
assert(call);
|
||||
python_calls_.emplace_back(call);
|
||||
|
||||
// If we're shutting down, just kill the call immediately.
|
||||
// (we turn all of our calls to no-ops as we shut down)
|
||||
if (shutting_down_) {
|
||||
Log("WARNING: adding call to expired activity; call will not function: "
|
||||
+ call->GetObjectDescription());
|
||||
call->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
void HostActivity::start() {
|
||||
if (_started) {
|
||||
Log("Error: Start called twice for activity.");
|
||||
}
|
||||
_started = true;
|
||||
}
|
||||
|
||||
auto HostActivity::GetAsHostActivity() -> HostActivity* { return this; }
|
||||
|
||||
auto HostActivity::NewMaterial(const std::string& name)
|
||||
-> Object::Ref<Material> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't create materials during activity shutdown");
|
||||
}
|
||||
|
||||
auto m(Object::New<Material>(name, scene()));
|
||||
materials_.emplace_back(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
auto HostActivity::GetTexture(const std::string& name) -> Object::Ref<Texture> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during activity shutdown");
|
||||
}
|
||||
return Media::GetMedia(&textures_, name, scene());
|
||||
}
|
||||
|
||||
auto HostActivity::GetSound(const std::string& name) -> Object::Ref<Sound> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during activity shutdown");
|
||||
}
|
||||
return Media::GetMedia(&sounds_, name, scene());
|
||||
}
|
||||
|
||||
auto HostActivity::GetData(const std::string& name) -> Object::Ref<Data> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during activity shutdown");
|
||||
}
|
||||
return Media::GetMedia(&datas_, name, scene());
|
||||
}
|
||||
|
||||
auto HostActivity::GetModel(const std::string& name) -> Object::Ref<Model> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during activity shutdown");
|
||||
}
|
||||
return Media::GetMedia(&models_, name, scene());
|
||||
}
|
||||
|
||||
auto HostActivity::GetCollideModel(const std::string& name)
|
||||
-> Object::Ref<CollideModel> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during activity shutdown");
|
||||
}
|
||||
return Media::GetMedia(&collide_models_, name, scene());
|
||||
}
|
||||
|
||||
void HostActivity::SetPaused(bool val) {
|
||||
if (paused_ == val) {
|
||||
return;
|
||||
}
|
||||
paused_ = val;
|
||||
UpdateStepTimerLength();
|
||||
}
|
||||
|
||||
void HostActivity::SetGameSpeed(float speed) {
|
||||
if (speed == game_speed_) {
|
||||
return;
|
||||
}
|
||||
assert(speed >= 0.0f);
|
||||
game_speed_ = speed;
|
||||
UpdateStepTimerLength();
|
||||
}
|
||||
|
||||
void HostActivity::UpdateStepTimerLength() {
|
||||
if (game_speed_ == 0.0f || paused_) {
|
||||
step_scene_timer_->SetLength(-1, true, base_time_);
|
||||
} else {
|
||||
step_scene_timer_->SetLength(
|
||||
std::max(1, static_cast<int>(
|
||||
round(static_cast<float>(kGameStepMilliseconds)
|
||||
/ (game_speed_ * g_game->debug_speed_mult())))),
|
||||
true, base_time_);
|
||||
}
|
||||
}
|
||||
|
||||
void HostActivity::HandleOutOfBoundsNodes() {
|
||||
if (scene()->out_of_bounds_nodes().empty()) {
|
||||
out_of_bounds_in_a_row_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure someone's handling our out-of-bounds messages.
|
||||
out_of_bounds_in_a_row_++;
|
||||
if (out_of_bounds_in_a_row_ > 100) {
|
||||
Log("Warning: 100 consecutive out-of-bounds messages sent."
|
||||
" They are probably not being handled properly");
|
||||
int j = 0;
|
||||
for (auto&& i : scene()->out_of_bounds_nodes()) {
|
||||
j++;
|
||||
Node* n = i.get();
|
||||
if (n) {
|
||||
std::string dstr;
|
||||
PyObject* delegate = n->GetDelegate();
|
||||
if (delegate) {
|
||||
dstr = PythonRef(delegate, PythonRef::kAcquire).Str();
|
||||
}
|
||||
Log(" node #" + std::to_string(j) + ": type='" + n->type()->name()
|
||||
+ "' addr=" + Utils::PtrToString(i.get()) + " name='" + n->label()
|
||||
+ "' delegate=" + dstr);
|
||||
}
|
||||
}
|
||||
out_of_bounds_in_a_row_ = 0;
|
||||
}
|
||||
|
||||
// Send out-of-bounds messages to newly out-of-bounds nodes.
|
||||
for (auto&& i : scene()->out_of_bounds_nodes()) {
|
||||
Node* n = i.get();
|
||||
if (n) {
|
||||
n->DispatchOutOfBoundsMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HostActivity::RegisterPyActivity(PyObject* pyActivityObj) {
|
||||
assert(pyActivityObj && pyActivityObj != Py_None);
|
||||
assert(!py_activity_weak_ref_.exists());
|
||||
|
||||
// Store a python weak-ref to this activity.
|
||||
py_activity_weak_ref_.Steal(PyWeakref_NewRef(pyActivityObj, nullptr));
|
||||
}
|
||||
|
||||
auto HostActivity::GetPyActivity() const -> PyObject* {
|
||||
PyObject* obj = py_activity_weak_ref_.get();
|
||||
if (!obj) return Py_None;
|
||||
return PyWeakref_GetObject(obj);
|
||||
}
|
||||
|
||||
auto HostActivity::GetHostSession() -> HostSession* {
|
||||
return host_session_.get();
|
||||
}
|
||||
|
||||
auto HostActivity::GetMutableScene() -> Scene* {
|
||||
Scene* sg = scene_.get();
|
||||
assert(sg);
|
||||
return sg;
|
||||
}
|
||||
|
||||
void HostActivity::SetIsForeground(bool val) {
|
||||
// If we're foreground, set our scene as foreground.
|
||||
Scene* sg = scene();
|
||||
if (val && sg) {
|
||||
// Set it locally.
|
||||
g_game->SetForegroundScene(sg);
|
||||
|
||||
// Also push it to clients.
|
||||
if (GameStream* out = GetGameStream()) {
|
||||
out->SetForegroundScene(scene_.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto HostActivity::globals_node() const -> GlobalsNode* {
|
||||
return globals_node_.get();
|
||||
}
|
||||
|
||||
auto HostActivity::NewSimTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
if (shutting_down_) {
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"WARNING: Creating game timer during host-activity shutdown");
|
||||
return 123; // Dummy.
|
||||
}
|
||||
if (length == 0 && repeat) {
|
||||
throw Exception("Can't add game-timer with length 0 and repeat on");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw Exception("Timer length cannot be < 0 (got " + std::to_string(length)
|
||||
+ ")");
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
Timer* t = sim_timers_.NewTimer(scene()->time(), length, offset,
|
||||
repeat ? -1 : 0, runnable);
|
||||
return t->id();
|
||||
}
|
||||
|
||||
auto HostActivity::NewBaseTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
if (shutting_down_) {
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"WARNING: Creating session-time timer during host-activity shutdown");
|
||||
return 123; // dummy...
|
||||
}
|
||||
if (length == 0 && repeat) {
|
||||
throw Exception("Can't add session-time timer with length 0 and repeat on");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw Exception("Timer length cannot be < 0");
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
Timer* t = base_timers_.NewTimer(base_time_, length, offset, repeat ? -1 : 0,
|
||||
runnable);
|
||||
return t->id();
|
||||
}
|
||||
|
||||
void HostActivity::DeleteSimTimer(int timer_id) {
|
||||
assert(InGameThread());
|
||||
if (shutting_down_) return;
|
||||
sim_timers_.DeleteTimer(timer_id);
|
||||
}
|
||||
|
||||
void HostActivity::DeleteBaseTimer(int timer_id) {
|
||||
assert(InGameThread());
|
||||
if (shutting_down_) return;
|
||||
base_timers_.DeleteTimer(timer_id);
|
||||
}
|
||||
|
||||
auto HostActivity::Update(millisecs_t time_advance) -> millisecs_t {
|
||||
assert(InGameThread());
|
||||
|
||||
// We can be killed at any time, so let's keep an eye out for that.
|
||||
WeakRef<HostActivity> test_ref(this);
|
||||
assert(test_ref.exists());
|
||||
|
||||
// If we haven't been told to start yet, don't do anything more.
|
||||
if (!_started) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
// Advance base time by the specified amount, stopping at all timers along the
|
||||
// way.
|
||||
millisecs_t target_base_time = base_time_ + time_advance;
|
||||
while (!base_timers_.empty()
|
||||
&& (base_time_ + base_timers_.GetTimeToNextExpire(base_time_)
|
||||
<= target_base_time)) {
|
||||
base_time_ += base_timers_.GetTimeToNextExpire(base_time_);
|
||||
base_timers_.Run(base_time_);
|
||||
if (!test_ref.exists()) {
|
||||
return 1000; // The last timer run might have killed us.
|
||||
}
|
||||
}
|
||||
base_time_ = target_base_time;
|
||||
|
||||
// Periodically prune various dead refs.
|
||||
if (base_time_ > next_prune_time_) {
|
||||
PruneDeadMapRefs(&textures_);
|
||||
PruneDeadMapRefs(&sounds_);
|
||||
PruneDeadMapRefs(&collide_models_);
|
||||
PruneDeadMapRefs(&models_);
|
||||
PruneDeadRefs(&materials_);
|
||||
PruneDeadRefs(&python_calls_);
|
||||
next_prune_time_ = base_time_ + 5000;
|
||||
}
|
||||
|
||||
// Return the time until the next timer goes off.
|
||||
return base_timers_.empty() ? 1000
|
||||
: base_timers_.GetTimeToNextExpire(base_time_);
|
||||
}
|
||||
|
||||
void HostActivity::ScreenSizeChanged() { scene()->ScreenSizeChanged(); }
|
||||
void HostActivity::LanguageChanged() { scene()->LanguageChanged(); }
|
||||
void HostActivity::DebugSpeedMultChanged() { UpdateStepTimerLength(); }
|
||||
void HostActivity::GraphicsQualityChanged(GraphicsQuality q) {
|
||||
scene()->GraphicsQualityChanged(q);
|
||||
}
|
||||
|
||||
void HostActivity::Draw(FrameDef* frame_def) {
|
||||
if (!_started) return;
|
||||
scene()->Draw(frame_def);
|
||||
}
|
||||
|
||||
void HostActivity::DumpFullState(GameStream* out) {
|
||||
// Add our scene.
|
||||
if (scene_.exists()) {
|
||||
scene_->Dump(out);
|
||||
}
|
||||
|
||||
// Before doing any nodes, we need to create all materials.
|
||||
// (but *not* their components, which may reference the nodes that we haven't
|
||||
// made yet)
|
||||
for (auto&& i : materials_) {
|
||||
if (Material* m = i.get()) {
|
||||
out->AddMaterial(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Add our media.
|
||||
for (auto&& i : textures_) {
|
||||
if (Texture* t = i.second.get()) {
|
||||
out->AddTexture(t);
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (Sound* s = i.second.get()) {
|
||||
out->AddSound(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (Model* s = i.second.get()) {
|
||||
out->AddModel(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : collide_models_) {
|
||||
if (CollideModel* m = i.second.get()) {
|
||||
out->AddCollideModel(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Add scene's nodes.
|
||||
if (scene_.exists()) {
|
||||
scene_->DumpNodes(out);
|
||||
}
|
||||
|
||||
// Ok, now we can fill out our materials since nodes/etc they reference
|
||||
// exists.
|
||||
for (auto&& i : materials_) {
|
||||
if (Material* m = i.get()) {
|
||||
m->DumpComponents(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto HostActivity::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
// Make sure the runnable passed in is reference-managed already.
|
||||
// (we may not add an initial reference ourself)
|
||||
assert(runnable->is_valid_refcounted_object());
|
||||
|
||||
// We currently support game and base timers.
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
return NewSimTimer(length, repeat, runnable);
|
||||
case TimeType::kBase:
|
||||
return NewBaseTimer(length, repeat, runnable);
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
return ContextTarget::NewTimer(timetype, length, repeat, runnable);
|
||||
}
|
||||
}
|
||||
|
||||
void HostActivity::DeleteTimer(TimeType timetype, int timer_id) {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
DeleteSimTimer(timer_id);
|
||||
break;
|
||||
case TimeType::kBase:
|
||||
DeleteBaseTimer(timer_id);
|
||||
break;
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
ContextTarget::DeleteTimer(timetype, timer_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto HostActivity::GetTime(TimeType timetype) -> millisecs_t {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
return scene()->time();
|
||||
case TimeType::kBase:
|
||||
return base_time();
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
return ContextTarget::GetTime(timetype);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
120
src/ballistica/game/host_activity.h
Normal file
120
src/ballistica/game/host_activity.h
Normal file
@ -0,0 +1,120 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_HOST_ACTIVITY_H_
|
||||
#define BALLISTICA_GAME_HOST_ACTIVITY_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/generic/timer_list.h"
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class HostActivity : public ContextTarget {
|
||||
public:
|
||||
explicit HostActivity(HostSession* host_session);
|
||||
~HostActivity() override;
|
||||
auto GetHostSession() -> HostSession* override;
|
||||
void SetGameSpeed(float speed);
|
||||
auto game_speed() const -> float { return game_speed_; }
|
||||
|
||||
// ContextTarget time/timer support.
|
||||
auto NewTimer(TimeType timetype, TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int override;
|
||||
void DeleteTimer(TimeType timetype, int timer_id) override;
|
||||
auto GetTime(TimeType timetype) -> millisecs_t override;
|
||||
|
||||
/// Return a borrowed ref to the python activity; Py_None if nonexistent.
|
||||
auto GetPyActivity() const -> PyObject*;
|
||||
|
||||
// All these commands are propagated into the output stream
|
||||
// in addition to being applied locally.
|
||||
auto NewMaterial(const std::string& name) -> Object::Ref<Material>;
|
||||
auto GetTexture(const std::string& name) -> Object::Ref<Texture> override;
|
||||
auto GetSound(const std::string& name) -> Object::Ref<Sound> override;
|
||||
auto GetData(const std::string& name) -> Object::Ref<Data> override;
|
||||
auto GetModel(const std::string& name) -> Object::Ref<Model> override;
|
||||
auto GetCollideModel(const std::string& name)
|
||||
-> Object::Ref<CollideModel> override;
|
||||
auto Update(millisecs_t time_advance) -> millisecs_t;
|
||||
auto base_time() const -> millisecs_t { return base_time_; }
|
||||
auto scene() -> Scene* {
|
||||
assert(scene_.exists());
|
||||
return scene_.get();
|
||||
}
|
||||
void start();
|
||||
|
||||
// A utility function; faster than dynamic_cast.
|
||||
auto GetAsHostActivity() -> HostActivity* override;
|
||||
auto GetMutableScene() -> Scene* override;
|
||||
void Draw(FrameDef* frame_def);
|
||||
void ScreenSizeChanged();
|
||||
void LanguageChanged();
|
||||
void DebugSpeedMultChanged();
|
||||
void GraphicsQualityChanged(GraphicsQuality q);
|
||||
|
||||
// Used to register python calls created in this context so we can make sure
|
||||
// they got properly cleaned up.
|
||||
void RegisterCall(PythonContextCall* call);
|
||||
auto shutting_down() const -> bool { return shutting_down_; }
|
||||
auto globals_node() const -> GlobalsNode*;
|
||||
void SetPaused(bool val);
|
||||
auto paused() const -> bool { return paused_; }
|
||||
void setAllowKickIdlePlayers(bool val) { allow_kick_idle_players_ = val; }
|
||||
auto getAllowKickIdlePlayers() const -> bool {
|
||||
return allow_kick_idle_players_;
|
||||
}
|
||||
auto GetGameStream() const -> GameStream*;
|
||||
void DumpFullState(GameStream* out);
|
||||
|
||||
private:
|
||||
auto NewSimTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
void DeleteSimTimer(int timer_id);
|
||||
auto NewBaseTimer(millisecs_t length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
void DeleteBaseTimer(int timer_id);
|
||||
void UpdateStepTimerLength();
|
||||
Object::WeakRef<GlobalsNode> globals_node_;
|
||||
void SetIsForeground(bool val);
|
||||
bool allow_kick_idle_players_ = false;
|
||||
void StepScene();
|
||||
Timer* step_scene_timer_ = nullptr;
|
||||
std::map<std::string, Object::WeakRef<Texture> > textures_;
|
||||
std::map<std::string, Object::WeakRef<Sound> > sounds_;
|
||||
std::map<std::string, Object::WeakRef<Data> > datas_;
|
||||
std::map<std::string, Object::WeakRef<CollideModel> > collide_models_;
|
||||
std::map<std::string, Object::WeakRef<Model> > models_;
|
||||
std::list<Object::WeakRef<Material> > materials_;
|
||||
bool shutting_down_ = false;
|
||||
|
||||
// Our list of python calls created in the context of this activity;
|
||||
// we clear them as we are shutting down and ensure nothing runs after
|
||||
// that point.
|
||||
std::list<Object::WeakRef<PythonContextCall> > python_calls_;
|
||||
millisecs_t next_prune_time_ = 0;
|
||||
bool _started = false;
|
||||
int out_of_bounds_in_a_row_ = 0;
|
||||
void HandleOutOfBoundsNodes();
|
||||
bool paused_ = false;
|
||||
float game_speed_ = 0.0f;
|
||||
millisecs_t base_time_ = 0;
|
||||
Object::Ref<Scene> scene_;
|
||||
Object::WeakRef<HostSession> host_session_;
|
||||
PythonRef py_activity_weak_ref_;
|
||||
void RegisterPyActivity(PyObject* pyActivity);
|
||||
|
||||
// Want this at the bottom so it dies first since this may cause python stuff
|
||||
// to access us.
|
||||
TimerList sim_timers_;
|
||||
TimerList base_timers_;
|
||||
friend class HostSession;
|
||||
friend class GlobalsNode;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_HOST_ACTIVITY_H_
|
||||
418
src/ballistica/game/player.cc
Normal file
418
src/ballistica/game/player.cc
Normal file
@ -0,0 +1,418 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/player.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/input/device/joystick.h"
|
||||
#include "ballistica/python/class/python_class_session_player.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
#include "ballistica/scene/node/node_attribute.h"
|
||||
#include "ballistica/scene/node/node_type.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
Player::Player(int id_in, HostSession* host_session)
|
||||
: id_(id_in), creation_time_(GetRealTime()), host_session_(host_session) {
|
||||
assert(host_session);
|
||||
assert(InGameThread());
|
||||
}
|
||||
|
||||
Player::~Player() {
|
||||
assert(InGameThread());
|
||||
|
||||
// If we have an input-device attached to us, detach it.
|
||||
InputDevice* input_device = input_device_.get();
|
||||
if (input_device) {
|
||||
input_device->DetachFromPlayer();
|
||||
}
|
||||
|
||||
// Release our ref to ourself if we have one.
|
||||
if (py_ref_) {
|
||||
Py_DECREF(py_ref_);
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::GetName(bool full, bool icon) const -> std::string {
|
||||
std::string n = full ? full_name_ : name_;
|
||||
|
||||
// Quasi-hacky: if they ask for no icon, strip the first char off our string
|
||||
// if its in the custom-use-range.
|
||||
if (!icon) {
|
||||
std::vector<uint32_t> uni = Utils::UnicodeFromUTF8(n, "3f94f4f");
|
||||
if (!uni.empty() && uni[0] >= 0xE000 && uni[0] <= 0xF8FF) {
|
||||
uni.erase(uni.begin());
|
||||
}
|
||||
return Utils::UTF8FromUnicode(uni);
|
||||
} else {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::GetHostActivity() const -> HostActivity* {
|
||||
return host_activity_.get();
|
||||
}
|
||||
|
||||
void Player::SetHostActivity(HostActivity* a) {
|
||||
assert(InGameThread());
|
||||
|
||||
// Make sure we get pulled out of one activity before being added to another.
|
||||
if (a && in_activity_) {
|
||||
std::string old_name =
|
||||
host_activity_.exists()
|
||||
? PythonRef(host_activity_->GetPyActivity(), PythonRef::kAcquire)
|
||||
.Str()
|
||||
: "<nullptr>";
|
||||
std::string new_name =
|
||||
PythonRef(a->GetPyActivity(), PythonRef::kAcquire).Str();
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"Player::SetHostActivity() called when already in an activity (old="
|
||||
+ old_name + ", new=" + new_name + ")");
|
||||
} else if (!a && !in_activity_) {
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"Player::SetHostActivity() called with nullptr when not in an "
|
||||
"activity");
|
||||
}
|
||||
host_activity_ = a;
|
||||
in_activity_ = (a != nullptr);
|
||||
}
|
||||
|
||||
void Player::SetPosition(const Vector3f& position) {
|
||||
position_ = position;
|
||||
have_position_ = true;
|
||||
}
|
||||
|
||||
void Player::ResetInput() {
|
||||
// Hold a ref to ourself while clearing this to make sure
|
||||
// we don't die midway as a result of freeing something.
|
||||
Object::Ref<Object> ref(this);
|
||||
calls_.clear();
|
||||
left_held_ = right_held_ = up_held_ = down_held_ = have_position_ = false;
|
||||
}
|
||||
|
||||
void Player::SetPyTeam(PyObject* team) {
|
||||
if (team != nullptr && team != Py_None) {
|
||||
// We store a weak-ref to this.
|
||||
py_team_weak_ref_.Steal(PyWeakref_NewRef(team, nullptr));
|
||||
} else {
|
||||
py_team_weak_ref_.Release();
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::GetPyTeam() -> PyObject* {
|
||||
PyObject* obj = py_team_weak_ref_.get();
|
||||
if (!obj) {
|
||||
return Py_None;
|
||||
}
|
||||
return PyWeakref_GetObject(obj);
|
||||
}
|
||||
|
||||
void Player::SetPyCharacter(PyObject* character) {
|
||||
if (character != nullptr && character != Py_None) {
|
||||
py_character_.Acquire(character);
|
||||
} else {
|
||||
py_character_.Release();
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::GetPyCharacter() -> PyObject* {
|
||||
return py_character_.exists() ? py_character_.get() : Py_None;
|
||||
}
|
||||
|
||||
void Player::SetPyColor(PyObject* c) { py_color_.Acquire(c); }
|
||||
auto Player::GetPyColor() -> PyObject* {
|
||||
return py_color_.exists() ? py_color_.get() : Py_None;
|
||||
}
|
||||
|
||||
void Player::SetPyHighlight(PyObject* c) { py_highlight_.Acquire(c); }
|
||||
auto Player::GetPyHighlight() -> PyObject* {
|
||||
return py_highlight_.exists() ? py_highlight_.get() : Py_None;
|
||||
}
|
||||
|
||||
void Player::SetPyActivityPlayer(PyObject* c) { py_activityplayer_.Acquire(c); }
|
||||
auto Player::GetPyActivityPlayer() -> PyObject* {
|
||||
return py_activityplayer_.exists() ? py_activityplayer_.get() : Py_None;
|
||||
}
|
||||
|
||||
auto Player::GetPyRef(bool new_ref) -> PyObject* {
|
||||
assert(InGameThread());
|
||||
if (py_ref_ == nullptr) {
|
||||
py_ref_ = PythonClassSessionPlayer::Create(this);
|
||||
}
|
||||
if (new_ref) {
|
||||
Py_INCREF(py_ref_);
|
||||
}
|
||||
return py_ref_;
|
||||
}
|
||||
|
||||
void Player::AssignInputCall(InputType type, PyObject* call_obj) {
|
||||
assert(InGameThread());
|
||||
assert(static_cast<int>(type) >= 0
|
||||
&& static_cast<int>(type) < static_cast<int>(InputType::kLast));
|
||||
|
||||
// Special case: if they're assigning hold-position-press or
|
||||
// hold-position-release, or any direction events, we add in a hold-position
|
||||
// press/release event before we deliver any other events.. that way newly
|
||||
// created stuff is informed of the hold state and doesn't wrongly think they
|
||||
// should start moving.
|
||||
switch (type) {
|
||||
case InputType::kHoldPositionPress:
|
||||
case InputType::kHoldPositionRelease:
|
||||
case InputType::kLeftPress:
|
||||
case InputType::kLeftRelease:
|
||||
case InputType::kRightPress:
|
||||
case InputType::kUpPress:
|
||||
case InputType::kUpRelease:
|
||||
case InputType::kDownPress:
|
||||
case InputType::kDownRelease:
|
||||
case InputType::kUpDown:
|
||||
case InputType::kLeftRight: {
|
||||
send_hold_state_ = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (call_obj) {
|
||||
calls_[static_cast<int>(type)] = Object::New<PythonContextCall>(call_obj);
|
||||
} else {
|
||||
calls_[static_cast<int>(type)].Clear();
|
||||
}
|
||||
|
||||
// If they assigned l/r, immediately send an update for its current value.
|
||||
if (type == InputType::kLeftRight) {
|
||||
RunInput(type, lr_state_);
|
||||
}
|
||||
|
||||
// Same for up/down.
|
||||
if (type == InputType::kUpDown) {
|
||||
RunInput(type, ud_state_);
|
||||
}
|
||||
|
||||
// Same for run.
|
||||
if (type == InputType::kRun) {
|
||||
RunInput(type, run_state_);
|
||||
}
|
||||
|
||||
// Same for fly.
|
||||
if (type == InputType::kFlyPress && fly_held_) {
|
||||
RunInput(type);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::RunInput(InputType type, float value) {
|
||||
assert(InGameThread());
|
||||
|
||||
const float threshold = kJoystickDiscreteThresholdFloat;
|
||||
|
||||
// Most input commands cause us to reset the player's time-out
|
||||
// there are a few exceptions though - very small analog values
|
||||
// get ignored since they can come through without user intervention.
|
||||
bool reset_time_out = true;
|
||||
if (type == InputType::kLeftRight || type == InputType::kUpDown) {
|
||||
if (std::abs(value) < 0.3f) {
|
||||
reset_time_out = false;
|
||||
}
|
||||
}
|
||||
if (type == InputType::kRun) {
|
||||
if (value < 0.3f) {
|
||||
reset_time_out = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Also ignore hold-position stuff since it can come through without user
|
||||
// interaction.
|
||||
if ((type == InputType::kHoldPositionPress)
|
||||
|| (type == InputType::kHoldPositionRelease))
|
||||
reset_time_out = false;
|
||||
|
||||
if (reset_time_out) {
|
||||
time_out_ = BA_PLAYER_TIME_OUT;
|
||||
}
|
||||
|
||||
// Keep track of the hold-position state that comes through here.
|
||||
// any-time hold position buttons are re-assigned, we subsequently
|
||||
// re-send the current hold-state so whatever its driving starts out correctly
|
||||
// held if need be.
|
||||
if (type == InputType::kHoldPositionPress) {
|
||||
hold_position_ = true;
|
||||
} else if (type == InputType::kHoldPositionRelease) {
|
||||
hold_position_ = false;
|
||||
} else if (type == InputType::kFlyPress) {
|
||||
fly_held_ = true;
|
||||
} else if (type == InputType::kFlyRelease) {
|
||||
fly_held_ = false;
|
||||
}
|
||||
|
||||
// If we were supposed to deliver hold-state, go ahead and do that first.
|
||||
if (send_hold_state_) {
|
||||
send_hold_state_ = false;
|
||||
if (hold_position_) {
|
||||
RunInput(InputType::kHoldPositionPress);
|
||||
} else {
|
||||
RunInput(InputType::kHoldPositionRelease);
|
||||
}
|
||||
}
|
||||
|
||||
// Let's make our life simpler by converting held-position-joystick-events..
|
||||
{
|
||||
// We need to store these since we might look at them during a hold-position
|
||||
// event when we don't have their originating events available.
|
||||
if (type == InputType::kLeftRight) {
|
||||
lr_state_ = value;
|
||||
}
|
||||
if (type == InputType::kUpDown) {
|
||||
ud_state_ = value;
|
||||
}
|
||||
if (type == InputType::kRun) {
|
||||
run_state_ = value;
|
||||
}
|
||||
|
||||
// Special input commands - keep track of left/right and up/down positions
|
||||
// so we can deliver simple "leftUp", "leftDown", etc type of events
|
||||
// in addition to the standard absolute leftRight positions, etc.
|
||||
if (type == InputType::kLeftRight || type == InputType::kHoldPositionPress
|
||||
|| type == InputType::kHoldPositionRelease) {
|
||||
float arg = lr_state_;
|
||||
if (hold_position_) {
|
||||
arg = 0.0f; // Throttle is off.
|
||||
}
|
||||
if (left_held_) {
|
||||
if (arg > -threshold) {
|
||||
left_held_ = false;
|
||||
RunInput(InputType::kLeftRelease);
|
||||
}
|
||||
} else if (right_held_) {
|
||||
if (arg < threshold) {
|
||||
right_held_ = false;
|
||||
RunInput(InputType::kRightRelease);
|
||||
}
|
||||
} else {
|
||||
if (arg >= threshold) {
|
||||
if (!left_held_ && !up_held_ && !down_held_) {
|
||||
right_held_ = true;
|
||||
RunInput(InputType::kRightPress);
|
||||
}
|
||||
} else if (arg <= -threshold) {
|
||||
if (!right_held_ && !up_held_ && !down_held_) {
|
||||
left_held_ = true;
|
||||
RunInput(InputType::kLeftPress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == InputType::kUpDown || type == InputType::kHoldPositionPress
|
||||
|| type == InputType::kHoldPositionRelease) {
|
||||
float arg = ud_state_;
|
||||
if (hold_position_) arg = 0.0f; // throttle is off;
|
||||
if (up_held_) {
|
||||
if (arg < threshold) {
|
||||
up_held_ = false;
|
||||
RunInput(InputType::kUpRelease);
|
||||
}
|
||||
} else if (down_held_) {
|
||||
if (arg > -threshold) {
|
||||
down_held_ = false;
|
||||
RunInput(InputType::kDownRelease);
|
||||
}
|
||||
} else {
|
||||
if (arg <= -threshold) {
|
||||
if (!left_held_ && !right_held_ && !up_held_) {
|
||||
down_held_ = true;
|
||||
RunInput(InputType::kDownPress);
|
||||
}
|
||||
} else if (arg >= threshold) {
|
||||
if (!left_held_ && !up_held_ && !right_held_) {
|
||||
up_held_ = true;
|
||||
RunInput(InputType::kUpPress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto j = calls_.find(static_cast<int>(type));
|
||||
if (j != calls_.end() && j->second.exists()) {
|
||||
if (type == InputType::kRun) {
|
||||
PythonRef args(
|
||||
Py_BuildValue("(f)", std::min(1.0f, std::max(0.0f, value))),
|
||||
PythonRef::kSteal);
|
||||
j->second->Run(args.get());
|
||||
} else if (type == InputType::kLeftRight || type == InputType::kUpDown) {
|
||||
PythonRef args(
|
||||
Py_BuildValue("(f)", std::min(1.0f, std::max(-1.0f, value))),
|
||||
PythonRef::kSteal);
|
||||
j->second->Run(args.get());
|
||||
} else {
|
||||
j->second->Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::GetHostSession() const -> HostSession* {
|
||||
return host_session_.get();
|
||||
}
|
||||
|
||||
void Player::SetName(const std::string& name, const std::string& full_name,
|
||||
bool is_real) {
|
||||
assert(InGameThread());
|
||||
HostSession* host_session = GetHostSession();
|
||||
BA_PRECONDITION(host_session);
|
||||
name_is_real_ = is_real;
|
||||
name_ = host_session->GetUnusedPlayerName(this, name);
|
||||
full_name_ = full_name;
|
||||
|
||||
// If we're already in the game and our name is changing, we need to update
|
||||
// the roster.
|
||||
if (accepted_) {
|
||||
g_game->UpdateGameRoster();
|
||||
}
|
||||
}
|
||||
|
||||
void Player::InputCommand(InputType type, float value) {
|
||||
assert(InGameThread());
|
||||
switch (type) {
|
||||
case InputType::kUpDown:
|
||||
case InputType::kLeftRight:
|
||||
case InputType::kRun:
|
||||
RunInput(type, value);
|
||||
break;
|
||||
// case InputType::kReset:
|
||||
// Log("Error: FIXME: player-input-reset command unimplemented");
|
||||
// break;
|
||||
default:
|
||||
RunInput(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Player::SetInputDevice(InputDevice* input_device) {
|
||||
input_device_ = input_device;
|
||||
}
|
||||
|
||||
auto Player::GetPublicAccountID() const -> std::string {
|
||||
assert(InGameThread());
|
||||
if (input_device_.exists()) {
|
||||
return input_device_->GetPublicAccountID();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void Player::SetIcon(const std::string& tex_name,
|
||||
const std::string& tint_tex_name,
|
||||
const std::vector<float>& tint_color,
|
||||
const std::vector<float>& tint2_color) {
|
||||
assert(tint_color.size() == 3);
|
||||
assert(tint2_color.size() == 3);
|
||||
icon_tex_name_ = tex_name;
|
||||
icon_tint_tex_name_ = tint_tex_name;
|
||||
icon_tint_color_ = tint_color;
|
||||
icon_tint2_color_ = tint2_color;
|
||||
icon_set_ = true;
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
167
src/ballistica/game/player.h
Normal file
167
src/ballistica/game/player.h
Normal file
@ -0,0 +1,167 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_PLAYER_H_
|
||||
#define BALLISTICA_GAME_PLAYER_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/input/input.h"
|
||||
#include "ballistica/math/vector3f.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
// How much time should pass before we kick idle players (in milliseconds).
|
||||
#define BA_PLAYER_TIME_OUT 60000
|
||||
#define BA_PLAYER_TIME_OUT_WARN 10000
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A player (from the game's point of view).
|
||||
class Player : public Object {
|
||||
public:
|
||||
Player(int id, HostSession* host_session);
|
||||
~Player() override;
|
||||
|
||||
void SetInputDevice(InputDevice* input_device);
|
||||
void AssignInputCall(InputType type, PyObject* call_obj);
|
||||
void InputCommand(InputType type, float value = 0.0f);
|
||||
|
||||
void SetName(const std::string& name, const std::string& full_name,
|
||||
bool real);
|
||||
auto GetName(bool full = false, bool icon = true) const -> std::string;
|
||||
auto name_is_real() const -> bool { return name_is_real_; }
|
||||
void ResetInput();
|
||||
auto GetHostSession() const -> HostSession*;
|
||||
|
||||
auto id() const -> int { return id_; }
|
||||
|
||||
auto NewPyRef() -> PyObject* { return GetPyRef(true); }
|
||||
auto BorrowPyRef() -> PyObject* { return GetPyRef(false); }
|
||||
|
||||
// Set the player node for the current activity.
|
||||
void set_node(Node* node) {
|
||||
assert(InGameThread());
|
||||
node_ = node;
|
||||
}
|
||||
auto node() const -> Node* {
|
||||
assert(InGameThread());
|
||||
return node_.get();
|
||||
}
|
||||
|
||||
void SetPyTeam(PyObject* team);
|
||||
auto GetPyTeam() -> PyObject*; // Returns a borrowed ref.
|
||||
|
||||
void SetPyCharacter(PyObject* team);
|
||||
auto GetPyCharacter() -> PyObject*; // Returns a borrowed ref.
|
||||
|
||||
void SetPyColor(PyObject* team);
|
||||
auto GetPyColor() -> PyObject*; // Returns a borrowed ref.
|
||||
|
||||
void SetPyHighlight(PyObject* team);
|
||||
auto GetPyHighlight() -> PyObject*; // Returns a borrowed ref.
|
||||
|
||||
void SetPyActivityPlayer(PyObject* team);
|
||||
auto GetPyActivityPlayer() -> PyObject*; // Returns a borrowed ref.
|
||||
|
||||
void set_has_py_data(bool has) { has_py_data_ = has; }
|
||||
auto has_py_data() const -> bool { return has_py_data_; }
|
||||
|
||||
auto GetInputDevice() const -> InputDevice* { return input_device_.get(); }
|
||||
auto GetAge() const -> millisecs_t { return GetRealTime() - creation_time_; }
|
||||
auto accepted() const -> bool { return accepted_; }
|
||||
|
||||
void SetPosition(const Vector3f& position);
|
||||
|
||||
// If an public account-id can be determined with relative
|
||||
// certainty for this player, returns it. Otherwise returns
|
||||
// an empty string.
|
||||
auto GetPublicAccountID() const -> std::string;
|
||||
|
||||
void SetHostActivity(HostActivity* host_activity);
|
||||
auto GetHostActivity() const -> HostActivity*;
|
||||
|
||||
auto has_py_ref() -> bool { return (py_ref_ != nullptr); }
|
||||
|
||||
void SetIcon(const std::string& tex_name, const std::string& tint_tex_name,
|
||||
const std::vector<float>& tint_color,
|
||||
const std::vector<float>& tint2_color);
|
||||
|
||||
auto icon_tex_name() const -> const std::string& {
|
||||
BA_PRECONDITION(icon_set_);
|
||||
return icon_tex_name_;
|
||||
}
|
||||
auto icon_tint_tex_name() const -> const std::string& {
|
||||
BA_PRECONDITION(icon_set_);
|
||||
return icon_tint_tex_name_;
|
||||
}
|
||||
auto icon_tint_color() const -> const std::vector<float>& {
|
||||
BA_PRECONDITION(icon_set_);
|
||||
return icon_tint_color_;
|
||||
}
|
||||
auto icon_tint2_color() const -> const std::vector<float>& {
|
||||
BA_PRECONDITION(icon_set_);
|
||||
return icon_tint2_color_;
|
||||
}
|
||||
void set_accepted(bool value) { accepted_ = value; }
|
||||
auto time_out() const -> millisecs_t { return time_out_; }
|
||||
void set_time_out(millisecs_t value) { time_out_ = value; }
|
||||
void set_have_position(bool value) { have_position_ = value; }
|
||||
|
||||
private:
|
||||
auto GetPyRef(bool new_ref) -> PyObject*;
|
||||
void RunInput(InputType type, float value = 0.0f);
|
||||
bool icon_set_{};
|
||||
std::string icon_tex_name_;
|
||||
std::string icon_tint_tex_name_;
|
||||
std::vector<float> icon_tint_color_;
|
||||
std::vector<float> icon_tint2_color_;
|
||||
Object::WeakRef<HostSession> host_session_;
|
||||
Object::WeakRef<HostActivity> host_activity_;
|
||||
Object::WeakRef<Node> node_;
|
||||
bool in_activity_{};
|
||||
Object::WeakRef<InputDevice> input_device_;
|
||||
PyObject* py_ref_{};
|
||||
bool accepted_{};
|
||||
bool has_py_data_{};
|
||||
millisecs_t creation_time_{};
|
||||
int id_{};
|
||||
std::string name_;
|
||||
std::string full_name_;
|
||||
|
||||
// Is the current name real (as opposed to a standin
|
||||
// title such as '<choosing player>')
|
||||
bool name_is_real_{};
|
||||
bool left_held_{};
|
||||
bool right_held_{};
|
||||
bool up_held_{};
|
||||
bool down_held_{};
|
||||
bool hold_position_{};
|
||||
bool send_hold_state_{};
|
||||
bool fly_held_{};
|
||||
float lr_state_{};
|
||||
float ud_state_{};
|
||||
float run_state_{};
|
||||
millisecs_t time_out_{BA_PLAYER_TIME_OUT};
|
||||
|
||||
// Player's position for use by input devices and whatnot for guides.
|
||||
// FIXME: This info should be acquired through the player node.
|
||||
bool have_position_{false};
|
||||
Vector3f position_{0.0f, 0.0f, 0.0f};
|
||||
|
||||
// These should be destructed before the rest of our class goes down,
|
||||
// so they should be here at the bottom..
|
||||
// (they might access our name string or other stuff declared above)
|
||||
// PythonRef py_actor_;
|
||||
PythonRef py_team_weak_ref_;
|
||||
PythonRef py_character_;
|
||||
PythonRef py_color_;
|
||||
PythonRef py_highlight_;
|
||||
PythonRef py_activityplayer_;
|
||||
std::map<int, Object::Ref<PythonContextCall> > calls_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_PLAYER_H_
|
||||
109
src/ballistica/game/player_spec.cc
Normal file
109
src/ballistica/game/player_spec.cc
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/player_spec.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/account.h"
|
||||
#include "ballistica/generic/json.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
PlayerSpec::PlayerSpec() : account_type_(AccountType::kInvalid) {}
|
||||
|
||||
PlayerSpec::PlayerSpec(const std::string& s) {
|
||||
cJSON* root_obj = cJSON_Parse(s.c_str());
|
||||
bool success = false;
|
||||
if (root_obj) {
|
||||
cJSON* name_obj = cJSON_GetObjectItem(root_obj, "n");
|
||||
cJSON* short_name_obj = cJSON_GetObjectItem(root_obj, "sn");
|
||||
cJSON* account_obj = cJSON_GetObjectItem(root_obj, "a");
|
||||
if (name_obj && short_name_obj && account_obj) {
|
||||
name_ = Utils::GetValidUTF8(name_obj->valuestring, "psps");
|
||||
short_name_ = Utils::GetValidUTF8(short_name_obj->valuestring, "psps2");
|
||||
|
||||
// Account type may technically be something we don't recognize,
|
||||
// but that's ok.. it'll just be 'invalid' to us in that case
|
||||
account_type_ = Account::AccountTypeFromString(account_obj->valuestring);
|
||||
success = true;
|
||||
}
|
||||
cJSON_Delete(root_obj);
|
||||
}
|
||||
if (!success) {
|
||||
Log("Error creating PlayerSpec from string: '" + s + "'");
|
||||
name_ = "<error>";
|
||||
short_name_ = "";
|
||||
account_type_ = AccountType::kInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
auto PlayerSpec::GetDisplayString() const -> std::string {
|
||||
return Account::AccountTypeToIconString(account_type_) + name_;
|
||||
}
|
||||
|
||||
auto PlayerSpec::GetShortName() const -> std::string {
|
||||
if (short_name_.empty()) {
|
||||
return name_;
|
||||
}
|
||||
return short_name_;
|
||||
}
|
||||
|
||||
auto PlayerSpec::operator==(const PlayerSpec& spec) const -> bool {
|
||||
// NOTE: need to add account ID in here once that's available
|
||||
return (spec.name_ == name_ && spec.short_name_ == short_name_
|
||||
&& spec.account_type_ == account_type_);
|
||||
}
|
||||
|
||||
auto PlayerSpec::GetSpecString() const -> std::string {
|
||||
cJSON* root;
|
||||
root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "n", name_.c_str());
|
||||
cJSON_AddStringToObject(root, "a",
|
||||
Account::AccountTypeToString(account_type_).c_str());
|
||||
cJSON_AddStringToObject(root, "sn", short_name_.c_str());
|
||||
char* out = cJSON_PrintUnformatted(root);
|
||||
std::string out_s = out;
|
||||
free(out);
|
||||
cJSON_Delete(root);
|
||||
|
||||
// We should never allow ourself to have all this add up to more than 256.
|
||||
assert(out_s.size() < 256);
|
||||
|
||||
return out_s;
|
||||
}
|
||||
|
||||
auto PlayerSpec::GetAccountPlayerSpec() -> PlayerSpec {
|
||||
PlayerSpec spec;
|
||||
if (g_account->GetAccountState() == AccountState::kSignedIn) {
|
||||
spec.account_type_ = g_app_globals->account_type;
|
||||
spec.name_ =
|
||||
Utils::GetValidUTF8(g_account->GetAccountName().c_str(), "bsgaps");
|
||||
} else {
|
||||
spec.name_ =
|
||||
Utils::GetValidUTF8(g_platform->GetDeviceName().c_str(), "bsgaps2");
|
||||
}
|
||||
if (spec.name_.size() > 100) {
|
||||
// FIXME should perhaps clamp this in unicode space
|
||||
Log("account name size too long: '" + spec.name_ + "'");
|
||||
spec.name_.resize(100);
|
||||
spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgaps3");
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
auto PlayerSpec::GetDummyPlayerSpec(const std::string& name) -> PlayerSpec {
|
||||
PlayerSpec spec;
|
||||
spec.name_ = Utils::GetValidUTF8(name.c_str(), "bsgdps1");
|
||||
if (spec.name_.size() > 100) {
|
||||
// FIXME should perhaps clamp this in unicode space
|
||||
Log("dummy player spec name too long: '" + spec.name_ + "'");
|
||||
spec.name_.resize(100);
|
||||
spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgdps2");
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
56
src/ballistica/game/player_spec.h
Normal file
56
src/ballistica/game/player_spec.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_PLAYER_SPEC_H_
|
||||
#define BALLISTICA_GAME_PLAYER_SPEC_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a PlayerSpec is a portable description of an entity such as a player or
|
||||
// client. It can contain long and short names, optional info linking it to a
|
||||
// real account, and can be passed around easily in string form.
|
||||
class PlayerSpec {
|
||||
public:
|
||||
// inits an invalid player-spec
|
||||
PlayerSpec();
|
||||
auto operator==(const PlayerSpec& spec) const -> bool;
|
||||
|
||||
// create a player-spec from a given spec-string.
|
||||
// in the case of an error, defaults will be used
|
||||
// (though the error will be reported)
|
||||
explicit PlayerSpec(const std::string& s);
|
||||
|
||||
// this returns a full display string for the spec,
|
||||
// which may include the account icon
|
||||
auto GetDisplayString() const -> std::string;
|
||||
|
||||
// returns a short version of the player's name
|
||||
// ideal for displaying in-game; this includes
|
||||
// no icon and may just be the first name
|
||||
auto GetShortName() const -> std::string;
|
||||
|
||||
// return the full string form to be passed around
|
||||
auto GetSpecString() const -> std::string;
|
||||
|
||||
// returns a PlayerSpec for the currently logged in account
|
||||
// if there is no current logged in account, a dummy-spec is created
|
||||
// using the device name (so this always returns something reasonable)
|
||||
static auto GetAccountPlayerSpec() -> PlayerSpec;
|
||||
|
||||
// returns a 'dummy' PlayerSpec using the given name; can be
|
||||
// used for non-account player profiles, names for non-logged-in
|
||||
// party hosts, etc.
|
||||
static auto GetDummyPlayerSpec(const std::string& name) -> PlayerSpec;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::string short_name_;
|
||||
AccountType account_type_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_PLAYER_SPEC_H_
|
||||
28
src/ballistica/game/score_to_beat.h
Normal file
28
src/ballistica/game/score_to_beat.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SCORE_TO_BEAT_H_
|
||||
#define BALLISTICA_GAME_SCORE_TO_BEAT_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Do we still need this?
|
||||
class ScoreToBeat {
|
||||
public:
|
||||
ScoreToBeat(std::string player_in, std::string type_in, std::string value_in,
|
||||
double timeIn)
|
||||
: player(std::move(player_in)),
|
||||
type(std::move(type_in)),
|
||||
value(std::move(value_in)),
|
||||
time(timeIn) {}
|
||||
std::string player;
|
||||
std::string type;
|
||||
std::string value;
|
||||
double time;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SCORE_TO_BEAT_H_
|
||||
1070
src/ballistica/game/session/client_session.cc
Normal file
1070
src/ballistica/game/session/client_session.cc
Normal file
File diff suppressed because it is too large
Load Diff
99
src/ballistica/game/session/client_session.h
Normal file
99
src/ballistica/game/session/client_session.h
Normal file
@ -0,0 +1,99 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SESSION_CLIENT_SESSION_H_
|
||||
#define BALLISTICA_GAME_SESSION_CLIENT_SESSION_H_
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/game/client_controller_interface.h"
|
||||
#include "ballistica/game/session/session.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class ClientSession : public Session {
|
||||
public:
|
||||
ClientSession();
|
||||
~ClientSession() override;
|
||||
|
||||
// Allows for things like replay speed.
|
||||
virtual auto GetActualTimeAdvance(int advance_in) -> int {
|
||||
return advance_in;
|
||||
}
|
||||
void Update(int time_advance) override;
|
||||
void Draw(FrameDef* f) override;
|
||||
virtual void HandleSessionMessage(const std::vector<uint8_t>& buffer);
|
||||
void Reset(bool rewind);
|
||||
auto GetForegroundContext() -> Context override;
|
||||
auto DoesFillScreen() const -> bool override;
|
||||
void ScreenSizeChanged() override;
|
||||
void LanguageChanged() override;
|
||||
auto shutting_down() const -> bool { return shutting_down_; }
|
||||
void GetCorrectionMessages(bool blend,
|
||||
std::vector<std::vector<uint8_t> >* messages);
|
||||
|
||||
// Called when attempting to step without input data available.
|
||||
virtual void OnCommandBufferUnderrun() {}
|
||||
|
||||
// Returns existing objects; throws exceptions if not available.
|
||||
auto GetScene(int id) const -> Scene*;
|
||||
auto GetNode(int id) const -> Node*;
|
||||
auto GetTexture(int id) const -> Texture*;
|
||||
auto GetModel(int id) const -> Model*;
|
||||
auto GetCollideModel(int id) const -> CollideModel*;
|
||||
auto GetMaterial(int id) const -> Material*;
|
||||
auto GetSound(int id) const -> Sound*;
|
||||
|
||||
protected:
|
||||
virtual void OnReset(bool rewind);
|
||||
virtual void FetchMessages() {}
|
||||
int steps_on_list_;
|
||||
std::list<std::vector<uint8_t> > commands_; // ready-to-go commands
|
||||
virtual void Error(const std::string& description);
|
||||
void End();
|
||||
millisecs_t base_time_;
|
||||
double target_base_time_ = 0.0f;
|
||||
bool shutting_down_;
|
||||
std::vector<int> least_buffered_count_list_; // move this to net-client?..
|
||||
std::vector<int> most_buffered_count_list_;
|
||||
int buffer_count_list_index_;
|
||||
int adjust_counter_;
|
||||
float correction_ = 1.0f;
|
||||
float largest_spike_smoothed_ = 0.0f;
|
||||
float low_pass_smoothed_ = 0.0f;
|
||||
|
||||
private:
|
||||
void ClearSessionObjs();
|
||||
void AddCommand(const std::vector<uint8_t>& command);
|
||||
|
||||
// commands being built up for the next time step
|
||||
// (we want to be able to run *everything* for a given timestep at once
|
||||
// to avoid drawing things in half-changed states, etc)
|
||||
std::list<std::vector<uint8_t> > commands_pending_; // commands for the next
|
||||
std::vector<uint8_t> current_cmd_;
|
||||
uint8_t* current_cmd_ptr_;
|
||||
auto ReadByte() -> uint8_t;
|
||||
auto ReadInt32() -> int32_t;
|
||||
void ReadInt32_2(int32_t* vals);
|
||||
void ReadInt32_3(int32_t* vals);
|
||||
void ReadInt32_4(int32_t* vals);
|
||||
auto ReadString() -> std::string;
|
||||
auto ReadFloat() -> float;
|
||||
void ReadFloats(int count, float* vals);
|
||||
void ReadInt32s(int count, int32_t* vals);
|
||||
void ReadChars(int count, char* vals);
|
||||
|
||||
protected:
|
||||
std::vector<Object::Ref<Scene> > scenes_;
|
||||
std::vector<Object::WeakRef<Node> > nodes_;
|
||||
std::vector<Object::Ref<Texture> > textures_;
|
||||
std::vector<Object::Ref<Model> > models_;
|
||||
std::vector<Object::Ref<Sound> > sounds_;
|
||||
std::vector<Object::Ref<CollideModel> > collide_models_;
|
||||
std::vector<Object::Ref<Material> > materials_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SESSION_CLIENT_SESSION_H_
|
||||
765
src/ballistica/game/session/host_session.cc
Normal file
765
src/ballistica/game/session/host_session.cc
Normal file
@ -0,0 +1,765 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/player.h"
|
||||
#include "ballistica/generic/lambda_runnable.h"
|
||||
#include "ballistica/generic/timer.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/media/component/data.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
#include "ballistica/media/component/sound.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_command.h"
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
HostSession::HostSession(PyObject* session_type_obj)
|
||||
: last_kick_idle_players_decrement_time_(GetRealTime()) {
|
||||
assert(g_game);
|
||||
assert(InGameThread());
|
||||
assert(session_type_obj != nullptr);
|
||||
|
||||
ScopedSetContext cp(this);
|
||||
|
||||
// FIXME: Should be an attr of the session class, not hard-coded.
|
||||
is_main_menu_ =
|
||||
static_cast<bool>(strstr(Python::ObjToString(session_type_obj).c_str(),
|
||||
"bastd.mainmenu.MainMenuSession"));
|
||||
// Log("MAIN MENU? " + std::to_string(is_main_menu()));
|
||||
|
||||
kick_idle_players_ = g_game->kick_idle_players();
|
||||
|
||||
// Create a timer to step our session scene.
|
||||
step_scene_timer_ =
|
||||
base_timers_.NewTimer(base_time_, kGameStepMilliseconds, 0, -1,
|
||||
NewLambdaRunnable([this] { StepScene(); }));
|
||||
|
||||
// Set up our output-stream, which will go to a replay and/or the network.
|
||||
// We don't dump to a replay if we're doing the main menu; that replay
|
||||
// would be boring.
|
||||
bool do_replay = !is_main_menu_;
|
||||
// Log("DO REPLAY? " + std::to_string(do_replay));
|
||||
|
||||
// At the moment headless-server don't write replays.
|
||||
#if BA_HEADLESS_BUILD
|
||||
do_replay = false;
|
||||
#endif // BA_HEADLESS_BUILD
|
||||
output_stream_ = Object::New<GameStream>(this, do_replay);
|
||||
|
||||
// Make a scene for our session-level nodes, etc.
|
||||
scene_ = Object::New<Scene>(0);
|
||||
if (output_stream_.exists()) {
|
||||
output_stream_->AddScene(scene_.get());
|
||||
}
|
||||
|
||||
// Fade in from our current blackness.
|
||||
g_graphics->FadeScreen(true, 250, nullptr);
|
||||
|
||||
// Start by showing the progress bar instead of hitching.
|
||||
g_graphics->EnableProgressBar(true);
|
||||
|
||||
// Now's a good time to run garbage collection; there should be pretty much
|
||||
// no game stuff to speak of in existence (provided the last session went
|
||||
// down peacefully).
|
||||
g_python->obj(Python::ObjID::kGarbageCollectCall).Call();
|
||||
|
||||
// Instantiate our python Session instance.
|
||||
PythonRef obj;
|
||||
PythonRef session_type(session_type_obj, PythonRef::kAcquire);
|
||||
{
|
||||
Python::ScopedCallLabel label("Session instantiation");
|
||||
obj = session_type.Call();
|
||||
}
|
||||
if (!obj.exists()) {
|
||||
throw Exception("Error creating game session: '" + session_type.Str()
|
||||
+ "'");
|
||||
}
|
||||
|
||||
// The session python object should have called
|
||||
// _ba.register_session() in its constructor to set session_py_obj_.
|
||||
if (session_py_obj_ != obj) {
|
||||
throw Exception("session not set up correctly");
|
||||
}
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case
|
||||
// it is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
}
|
||||
|
||||
auto HostSession::GetHostSession() -> HostSession* { return this; }
|
||||
|
||||
void HostSession::DestroyHostActivity(HostActivity* a) {
|
||||
BA_PRECONDITION(a);
|
||||
BA_PRECONDITION(a->GetHostSession() == this);
|
||||
if (a == foreground_host_activity_.get()) {
|
||||
foreground_host_activity_.Clear();
|
||||
}
|
||||
|
||||
// Clear it from our activities list if its still on there.
|
||||
for (auto i = host_activities_.begin(); i < host_activities_.end(); i++) {
|
||||
if (i->get() == a) {
|
||||
host_activities_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The only reason it wouldn't be there should be because the activity is
|
||||
// dying due our clearing of the list in our destructor; make sure that's
|
||||
// the case.
|
||||
assert(shutting_down_);
|
||||
}
|
||||
|
||||
auto HostSession::GetMutableScene() -> Scene* {
|
||||
assert(scene_.exists());
|
||||
return scene_.get();
|
||||
}
|
||||
|
||||
void HostSession::DebugSpeedMultChanged() {
|
||||
// FIXME - should we progress our own scene faster/slower depending on
|
||||
// this too? Is there really a need to?
|
||||
|
||||
// Let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->DebugSpeedMultChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::ScreenSizeChanged() {
|
||||
// Let our internal scene know.
|
||||
scene()->ScreenSizeChanged();
|
||||
|
||||
// Also let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->ScreenSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::LanguageChanged() {
|
||||
// Let our internal scene know.
|
||||
scene()->LanguageChanged();
|
||||
|
||||
// Also let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->LanguageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::GraphicsQualityChanged(GraphicsQuality q) {
|
||||
// Let our internal scene know.
|
||||
scene()->GraphicsQualityChanged(q);
|
||||
|
||||
// Let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->GraphicsQualityChanged(q);
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::DoesFillScreen() const -> bool {
|
||||
// FIXME not necessarily the case.
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostSession::Draw(FrameDef* f) {
|
||||
// First draw our session scene.
|
||||
scene()->Draw(f);
|
||||
|
||||
// Let all our activities draw their own scenes/etc.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->Draw(f);
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::NewTimer(TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
if (shutting_down_) {
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"WARNING: Creating game timer during host-session shutdown");
|
||||
return 123; // dummy...
|
||||
}
|
||||
if (length == 0 && repeat) {
|
||||
throw Exception("Can't add game-timer with length 0 and repeat on");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw Exception("Timer length cannot be < 0 (got " + std::to_string(length)
|
||||
+ ")");
|
||||
}
|
||||
int offset = 0;
|
||||
Timer* t = sim_timers_.NewTimer(scene()->time(), length, offset,
|
||||
repeat ? -1 : 0, runnable);
|
||||
return t->id();
|
||||
}
|
||||
|
||||
void HostSession::DeleteTimer(int timer_id) {
|
||||
assert(InGameThread());
|
||||
if (shutting_down_) return;
|
||||
sim_timers_.DeleteTimer(timer_id);
|
||||
}
|
||||
|
||||
auto HostSession::GetSound(const std::string& name) -> Object::Ref<Sound> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&sounds_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetData(const std::string& name) -> Object::Ref<Data> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&datas_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetTexture(const std::string& name) -> Object::Ref<Texture> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&textures_, name, scene());
|
||||
}
|
||||
auto HostSession::GetModel(const std::string& name) -> Object::Ref<Model> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load media during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&models_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetForegroundContext() -> Context {
|
||||
HostActivity* a = foreground_host_activity_.get();
|
||||
if (a) {
|
||||
return Context(a);
|
||||
}
|
||||
return Context(this);
|
||||
}
|
||||
|
||||
void HostSession::RequestPlayer(InputDevice* device) {
|
||||
assert(InGameThread());
|
||||
|
||||
// Ignore if we have no python session obj.
|
||||
if (!GetSessionPyObj()) {
|
||||
Log("Error: HostSession::RequestPlayer() called w/no session_py_obj_.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to at least temporarily create and attach to a player for passing to
|
||||
// the callback.
|
||||
int player_id = next_player_id_++;
|
||||
auto player(Object::New<Player>(player_id, this));
|
||||
players_.push_back(player);
|
||||
device->AttachToLocalPlayer(player.get());
|
||||
|
||||
// Ask the python layer to accept/deny this guy.
|
||||
bool accept;
|
||||
{
|
||||
// Set the session as context.
|
||||
ScopedSetContext cp(this);
|
||||
accept = static_cast<bool>(
|
||||
session_py_obj_.GetAttr("_request_player")
|
||||
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
|
||||
PythonRef::kSteal))
|
||||
.ValueAsInt());
|
||||
if (accept) {
|
||||
player->set_accepted(true);
|
||||
} else {
|
||||
RemovePlayer(player.get());
|
||||
}
|
||||
}
|
||||
|
||||
// If he was accepted, update our game roster with the new info.
|
||||
if (accept) {
|
||||
g_game->UpdateGameRoster();
|
||||
}
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case it
|
||||
// is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
}
|
||||
|
||||
void HostSession::RemovePlayer(Player* player) {
|
||||
assert(player);
|
||||
|
||||
for (auto i = players_.begin(); i != players_.end(); ++i) {
|
||||
if (i->get() == player) {
|
||||
// Grab a ref to keep the player alive, pull him off the list, then call
|
||||
// his leaving callback.
|
||||
Object::Ref<Player> player2 = *i;
|
||||
players_.erase(i);
|
||||
|
||||
// Only make the callback for this player if they were accepted.
|
||||
if (player2->accepted()) {
|
||||
IssuePlayerLeft(player2.get());
|
||||
}
|
||||
|
||||
// Update our game roster with the departure.
|
||||
g_game->UpdateGameRoster();
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case
|
||||
// it is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
BA_LOG_ERROR_TRACE("Player not found in HostSession::RemovePlayer()");
|
||||
}
|
||||
|
||||
void HostSession::IssuePlayerLeft(Player* player) {
|
||||
assert(player);
|
||||
assert(InGameThread());
|
||||
|
||||
try {
|
||||
if (GetSessionPyObj()) {
|
||||
if (player) {
|
||||
// Make sure we're the context for session callbacks.
|
||||
ScopedSetContext cp(this);
|
||||
Python::ScopedCallLabel label("Session on_player_leave");
|
||||
session_py_obj_.GetAttr("on_player_leave")
|
||||
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
|
||||
PythonRef::kSteal));
|
||||
} else {
|
||||
BA_LOG_PYTHON_TRACE_ONCE("missing player on IssuePlayerLeft");
|
||||
}
|
||||
} else {
|
||||
Log("WARNING: HostSession: IssuePlayerLeft caled with no "
|
||||
"session_py_obj_");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Log(std::string("Error calling on_player_leave(): ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::SetKickIdlePlayers(bool enable) {
|
||||
// If this has changed, reset our disconnect-time reporting.
|
||||
assert(InGameThread());
|
||||
if (enable != kick_idle_players_) {
|
||||
last_kick_idle_players_decrement_time_ = GetRealTime();
|
||||
}
|
||||
kick_idle_players_ = enable;
|
||||
}
|
||||
|
||||
void HostSession::SetForegroundHostActivity(HostActivity* a) {
|
||||
assert(a);
|
||||
assert(InGameThread());
|
||||
|
||||
if (shutting_down_) {
|
||||
Log("WARNING: SetForegroundHostActivity called during session shutdown; "
|
||||
"ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check: make sure the one provided is part of this session.
|
||||
bool found = false;
|
||||
for (auto&& i : host_activities_) {
|
||||
if (i == a) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((a->GetHostSession() != this) || !found) {
|
||||
throw Exception("HostActivity is not part of this HostSession");
|
||||
}
|
||||
|
||||
foreground_host_activity_ = a;
|
||||
|
||||
// Now go through telling each host-activity whether it's foregrounded or not.
|
||||
// FIXME: Dying sessions never get told they're un-foregrounded.. could that
|
||||
// ever be a problem?
|
||||
bool session_is_foreground = (g_game->GetForegroundSession() != nullptr);
|
||||
for (auto&& i : host_activities_) {
|
||||
i->SetIsForeground(session_is_foreground && (i == a));
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::AddHostActivity(HostActivity* a) {
|
||||
host_activities_.emplace_back(a);
|
||||
}
|
||||
|
||||
// Called by the constructor of the session python object.
|
||||
void HostSession::RegisterPySession(PyObject* obj) {
|
||||
session_py_obj_.Acquire(obj);
|
||||
}
|
||||
|
||||
// Given an activity python type, instantiates and returns a new activity.
|
||||
auto HostSession::NewHostActivity(PyObject* activity_type_obj,
|
||||
PyObject* settings_obj) -> PyObject* {
|
||||
PythonRef activity_type(activity_type_obj, PythonRef::kAcquire);
|
||||
if (!activity_type.CallableCheck()) {
|
||||
throw Exception("Invalid HostActivity type passed; not callable");
|
||||
}
|
||||
|
||||
// First generate our C++ activity instance and point the context at it.
|
||||
auto activity(Object::New<HostActivity>(this));
|
||||
AddHostActivity(activity.get());
|
||||
|
||||
ScopedSetContext cp(activity.get());
|
||||
|
||||
// Now instantiate the python instance.. pass args if some were provided, or
|
||||
// an empty dict otherwise.
|
||||
PythonRef args;
|
||||
if (settings_obj == Py_None) {
|
||||
args.Steal(Py_BuildValue("({})"));
|
||||
} else {
|
||||
args.Steal(Py_BuildValue("(O)", settings_obj));
|
||||
}
|
||||
|
||||
PythonRef result = activity_type.Call(args);
|
||||
if (!result.exists()) {
|
||||
throw Exception("HostActivity creation failed");
|
||||
}
|
||||
|
||||
// If all went well, the python activity constructor should have called
|
||||
// _ba.register_activity(), so we should be able to get at the same python
|
||||
// activity we just instantiated through the c++ class.
|
||||
if (activity->GetPyActivity() != result.get()) {
|
||||
throw Exception("Error on HostActivity construction");
|
||||
}
|
||||
|
||||
PyObject* obj = result.get();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
auto HostSession::RegisterPyActivity(PyObject* activity_obj) -> HostActivity* {
|
||||
// The context should be pointing to an unregistered HostActivity;
|
||||
// register and return it.
|
||||
HostActivity* activity = Context::current().GetHostActivity();
|
||||
if (!activity)
|
||||
throw Exception(
|
||||
"No current activity in RegisterPyActivity; did you remember to call "
|
||||
"ba.newHostActivity() to instantiate your activity?");
|
||||
activity->RegisterPyActivity(activity_obj);
|
||||
return activity;
|
||||
}
|
||||
|
||||
void HostSession::DecrementPlayerTimeOuts(millisecs_t millisecs) {
|
||||
for (auto&& i : players_) {
|
||||
Player* player = i.get();
|
||||
assert(player);
|
||||
if (player->time_out() < millisecs) {
|
||||
std::string kick_str =
|
||||
g_game->GetResourceString("kickIdlePlayersKickedText");
|
||||
Utils::StringReplaceOne(&kick_str, "${NAME}", player->GetName());
|
||||
ScreenMessage(kick_str);
|
||||
RemovePlayer(player);
|
||||
return; // Bail for this round since we prolly mucked with the list.
|
||||
} else if (player->time_out() > BA_PLAYER_TIME_OUT_WARN
|
||||
&& (player->time_out() - millisecs <= BA_PLAYER_TIME_OUT_WARN)) {
|
||||
std::string kick_str_1 =
|
||||
g_game->GetResourceString("kickIdlePlayersWarning1Text");
|
||||
Utils::StringReplaceOne(&kick_str_1, "${NAME}", player->GetName());
|
||||
Utils::StringReplaceOne(&kick_str_1, "${COUNT}",
|
||||
std::to_string(BA_PLAYER_TIME_OUT_WARN / 1000));
|
||||
ScreenMessage(kick_str_1);
|
||||
ScreenMessage(g_game->GetResourceString("kickIdlePlayersWarning2Text"));
|
||||
}
|
||||
player->set_time_out(player->time_out() - millisecs);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::ProcessPlayerTimeOuts() {
|
||||
millisecs_t real_time = GetRealTime();
|
||||
|
||||
if (foreground_host_activity_.exists()
|
||||
&& foreground_host_activity_->game_speed() > 0.0
|
||||
&& !foreground_host_activity_->paused()
|
||||
&& foreground_host_activity_->getAllowKickIdlePlayers()
|
||||
&& kick_idle_players_) {
|
||||
// Let's only do this every now and then.
|
||||
if (real_time - last_kick_idle_players_decrement_time_ > 1000) {
|
||||
DecrementPlayerTimeOuts(real_time
|
||||
- last_kick_idle_players_decrement_time_);
|
||||
last_kick_idle_players_decrement_time_ = real_time;
|
||||
}
|
||||
} else {
|
||||
// If we're not kicking, we still store the latest time (so it doesnt
|
||||
// accumulate for when we start again).
|
||||
last_kick_idle_players_decrement_time_ = real_time;
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::StepScene() {
|
||||
// Run up our game-time timers.
|
||||
sim_timers_.Run(scene()->time());
|
||||
|
||||
// And step.
|
||||
scene()->Step();
|
||||
}
|
||||
|
||||
void HostSession::Update(int time_advance) {
|
||||
assert(InGameThread());
|
||||
|
||||
// We can be killed at any time, so let's keep an eye out for that.
|
||||
WeakRef<HostSession> test_ref(this);
|
||||
assert(test_ref.exists());
|
||||
|
||||
ProcessPlayerTimeOuts();
|
||||
|
||||
GameStream* output_stream = GetGameStream();
|
||||
|
||||
// Advance base time by the specified amount,
|
||||
// stopping at all timers along the way.
|
||||
millisecs_t target_base_time = base_time_ + time_advance;
|
||||
while (!base_timers_.empty()
|
||||
&& (base_time_ + base_timers_.GetTimeToNextExpire(base_time_)
|
||||
<= target_base_time)) {
|
||||
base_time_ += base_timers_.GetTimeToNextExpire(base_time_);
|
||||
if (output_stream) {
|
||||
output_stream->SetTime(base_time_);
|
||||
}
|
||||
base_timers_.Run(base_time_);
|
||||
}
|
||||
base_time_ = target_base_time;
|
||||
if (output_stream) {
|
||||
output_stream->SetTime(base_time_);
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
|
||||
// Update our activities (iterate via weak-refs as this list may change under
|
||||
// us at any time).
|
||||
std::vector<Object::WeakRef<HostActivity> > activities =
|
||||
PointersToWeakRefs(RefsToPointers(host_activities_));
|
||||
for (auto&& i : activities) {
|
||||
if (i.exists()) {
|
||||
i->Update(time_advance);
|
||||
assert(test_ref.exists());
|
||||
}
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
|
||||
// Periodically prune various dead refs.
|
||||
if (base_time_ > next_prune_time_) {
|
||||
PruneDeadMapRefs(&textures_);
|
||||
PruneDeadMapRefs(&sounds_);
|
||||
PruneDeadMapRefs(&models_);
|
||||
PruneDeadRefs(&python_calls_);
|
||||
next_prune_time_ = base_time_ + 5000;
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
}
|
||||
|
||||
HostSession::~HostSession() {
|
||||
try {
|
||||
shutting_down_ = true;
|
||||
|
||||
// Put the scene in shut-down mode before we start killing stuff
|
||||
// (this generates warnings, suppresses messages, etc).
|
||||
scene_->set_shutting_down(true);
|
||||
|
||||
// Clear out all python calls registered in our context
|
||||
// (should wipe out refs to our session and prevent them from running
|
||||
// without a valid session context).
|
||||
for (auto&& i : python_calls_) {
|
||||
if (i.exists()) {
|
||||
i->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all our media dead to clear it out of our output-stream cleanly.
|
||||
for (auto&& i : textures_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear our timers and scene; this should wipe out any remaining refs
|
||||
// to our session scene.
|
||||
base_timers_.Clear();
|
||||
sim_timers_.Clear();
|
||||
scene_.Clear();
|
||||
|
||||
// Kill our python session object.
|
||||
{
|
||||
ScopedSetContext cp(this);
|
||||
session_py_obj_.Release();
|
||||
}
|
||||
|
||||
// Kill any remaining activity data. Generally all activities should die
|
||||
// when the session python object goes down, but lets clean up in case any
|
||||
// didn't.
|
||||
for (auto&& i : host_activities_) {
|
||||
ScopedSetContext cp{Object::Ref<ContextTarget>(i)};
|
||||
i.Clear();
|
||||
}
|
||||
|
||||
// Report outstanding calls. There shouldn't be any at this point. Actually
|
||||
// it turns out there's generally 1; whichever call was responsible for
|
||||
// killing this activity will still be in progress.. so let's report on 2 or
|
||||
// more I guess.
|
||||
#if BA_DEBUG_BUILD
|
||||
PruneDeadRefs(&python_calls_);
|
||||
if (python_calls_.size() > 1) {
|
||||
std::string s = "WARNING: " + std::to_string(python_calls_.size())
|
||||
+ " live PythonContextCalls at shutdown for "
|
||||
+ "HostSession" + " (1 call is expected):";
|
||||
int count = 1;
|
||||
for (auto&& i : python_calls_) {
|
||||
s += ("\n " + std::to_string(count++) + ": "
|
||||
+ i->GetObjectDescription());
|
||||
}
|
||||
Log(s);
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
} catch (const std::exception& e) {
|
||||
Log("Exception in HostSession destructor: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::RegisterCall(PythonContextCall* call) {
|
||||
assert(call);
|
||||
python_calls_.emplace_back(call);
|
||||
|
||||
// If we're shutting down, just kill the call immediately.
|
||||
// (we turn all of our calls to no-ops as we shut down).
|
||||
if (shutting_down_) {
|
||||
Log("WARNING: adding call to expired session; call will not function: "
|
||||
+ call->GetObjectDescription());
|
||||
call->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::GetUnusedPlayerName(Player* p, const std::string& base_name)
|
||||
-> std::string {
|
||||
// Now find the first non-taken variation.
|
||||
int index = 1;
|
||||
std::string name_test;
|
||||
while (true) {
|
||||
if (index > 1) {
|
||||
name_test = base_name + " " + std::to_string(index);
|
||||
} else {
|
||||
name_test = base_name;
|
||||
}
|
||||
bool name_found = false;
|
||||
for (auto&& j : players_) {
|
||||
if ((j->GetName() == name_test) && (j.get() != p)) {
|
||||
name_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!name_found) break;
|
||||
index += 1;
|
||||
}
|
||||
return name_test;
|
||||
}
|
||||
|
||||
void HostSession::DumpFullState(GameStream* out) {
|
||||
// Add session-scene.
|
||||
if (scene_.exists()) {
|
||||
scene_->Dump(out);
|
||||
}
|
||||
|
||||
// Dump media associated with session-scene.
|
||||
for (auto&& i : textures_) {
|
||||
if (Texture* t = i.second.get()) {
|
||||
out->AddTexture(t);
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (Sound* s = i.second.get()) {
|
||||
out->AddSound(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (Model* s = i.second.get()) {
|
||||
out->AddModel(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Dump session-scene's nodes.
|
||||
if (scene_.exists()) {
|
||||
scene_->DumpNodes(out);
|
||||
}
|
||||
|
||||
// Now let our activities dump themselves.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->DumpFullState(out);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::GetCorrectionMessages(
|
||||
bool blend, std::vector<std::vector<uint8_t> >* messages) {
|
||||
std::vector<uint8_t> message;
|
||||
|
||||
// Grab correction for session scene (though there shouldn't be one).
|
||||
if (scene_.exists()) {
|
||||
message = scene_->GetCorrectionMessage(blend);
|
||||
if (message.size() > 4) {
|
||||
// A correction packet of size 4 is empty; ignore it.
|
||||
messages->push_back(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Now do same for activity scenes.
|
||||
for (auto&& i : host_activities_) {
|
||||
if (HostActivity* ha = i.get()) {
|
||||
if (Scene* sg = ha->scene()) {
|
||||
message = sg->GetCorrectionMessage(blend);
|
||||
if (message.size() > 4) {
|
||||
// A correction packet of size 4 is empty; ignore it.
|
||||
messages->push_back(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
// Make sure the runnable passed in is reference-managed already
|
||||
// (we may not add an initial reference ourself).
|
||||
assert(runnable->is_valid_refcounted_object());
|
||||
|
||||
// We currently support game and base timers.
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
// Game and base timers are the same thing for us.
|
||||
return NewTimer(length, repeat, runnable);
|
||||
default:
|
||||
// Gall back to default for descriptive error otherwise.
|
||||
return ContextTarget::NewTimer(timetype, length, repeat, runnable);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::DeleteTimer(TimeType timetype, int timer_id) {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
// Game and base timers are the same thing for us.
|
||||
DeleteTimer(timer_id);
|
||||
break;
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
ContextTarget::DeleteTimer(timetype, timer_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::GetTime(TimeType timetype) -> millisecs_t {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
return scene_->time();
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
return ContextTarget::GetTime(timetype);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
131
src/ballistica/game/session/host_session.h
Normal file
131
src/ballistica/game/session/host_session.h
Normal file
@ -0,0 +1,131 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SESSION_HOST_SESSION_H_
|
||||
#define BALLISTICA_GAME_SESSION_HOST_SESSION_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/game/session/session.h"
|
||||
#include "ballistica/generic/timer_list.h"
|
||||
#include "ballistica/python/python_ref.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class HostSession : public Session {
|
||||
public:
|
||||
explicit HostSession(PyObject* session_type_obj);
|
||||
~HostSession() override;
|
||||
|
||||
// Return a borrowed python ref.
|
||||
auto GetSessionPyObj() const -> PyObject* { return session_py_obj_.get(); }
|
||||
|
||||
// Set focus to a Context (it must belong to this session).
|
||||
void SetForegroundHostActivity(HostActivity* sgc);
|
||||
auto GetSound(const std::string& name) -> Object::Ref<Sound> override;
|
||||
auto GetData(const std::string& name) -> Object::Ref<Data> override;
|
||||
auto GetTexture(const std::string& name) -> Object::Ref<Texture> override;
|
||||
auto GetModel(const std::string& name) -> Object::Ref<Model> override;
|
||||
|
||||
void SetKickIdlePlayers(bool enable);
|
||||
|
||||
// Update the session.
|
||||
void Update(int time_advance) override;
|
||||
|
||||
// ContextTarget time/timer support
|
||||
auto NewTimer(TimeType timetype, TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int override;
|
||||
void DeleteTimer(TimeType timetype, int timer_id) override;
|
||||
auto GetTime(TimeType timetype) -> millisecs_t override;
|
||||
|
||||
// Given an activity python type, instantiate a new activity
|
||||
// and return a new reference.
|
||||
auto NewHostActivity(PyObject* activity_type_obj, PyObject* settings_obj)
|
||||
-> PyObject*;
|
||||
void DestroyHostActivity(HostActivity* a);
|
||||
void RemovePlayer(Player* player);
|
||||
void RequestPlayer(InputDevice* device);
|
||||
|
||||
// Return either a host-activity context or the session-context.
|
||||
auto GetForegroundContext() -> Context override;
|
||||
auto DoesFillScreen() const -> bool override;
|
||||
void Draw(FrameDef* f) override;
|
||||
void ScreenSizeChanged() override;
|
||||
void LanguageChanged() override;
|
||||
void GraphicsQualityChanged(GraphicsQuality q) override;
|
||||
void DebugSpeedMultChanged() override;
|
||||
auto GetHostSession() -> HostSession* override;
|
||||
auto GetMutableScene() -> Scene* override;
|
||||
auto scene() -> Scene* {
|
||||
assert(scene_.exists());
|
||||
return scene_.get();
|
||||
}
|
||||
void RegisterCall(PythonContextCall* call);
|
||||
auto GetGameStream() const -> GameStream* { return output_stream_.get(); }
|
||||
auto is_main_menu() const -> bool {
|
||||
return is_main_menu_;
|
||||
} // fixme remove this
|
||||
void DumpFullState(GameStream* out) override;
|
||||
void GetCorrectionMessages(bool blend,
|
||||
std::vector<std::vector<uint8_t> >* messages);
|
||||
auto base_time() const -> millisecs_t { return base_time_; }
|
||||
auto players() const -> const std::vector<Object::Ref<Player> >& {
|
||||
return players_;
|
||||
}
|
||||
|
||||
// Called by new py Session to pass themselves to us.
|
||||
void RegisterPySession(PyObject* obj);
|
||||
|
||||
// Called by new py Activities to pass themselves to us.
|
||||
auto RegisterPyActivity(PyObject* activity_obj) -> HostActivity*;
|
||||
|
||||
// New HostActivities should call this in their constructors.
|
||||
void AddHostActivity(HostActivity* sgc);
|
||||
|
||||
auto GetUnusedPlayerName(Player* p, const std::string& base_name)
|
||||
-> std::string;
|
||||
|
||||
private:
|
||||
auto NewTimer(TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int;
|
||||
void DeleteTimer(int timer_id);
|
||||
void StepScene();
|
||||
void ProcessPlayerTimeOuts();
|
||||
void DecrementPlayerTimeOuts(millisecs_t millisecs);
|
||||
void IssuePlayerLeft(Player* player);
|
||||
|
||||
bool is_main_menu_; // FIXME: Remove this.
|
||||
Object::Ref<GameStream> output_stream_;
|
||||
Timer* step_scene_timer_;
|
||||
millisecs_t base_time_ = 0;
|
||||
TimerList sim_timers_;
|
||||
TimerList base_timers_;
|
||||
Object::Ref<Scene> scene_;
|
||||
bool shutting_down_ = false;
|
||||
|
||||
// Our list of python calls created in the context of this activity. We
|
||||
// clear them as we are shutting down and ensure nothing runs after that
|
||||
// point.
|
||||
std::list<Object::WeakRef<PythonContextCall> > python_calls_;
|
||||
std::vector<Object::Ref<Player> > players_;
|
||||
int next_player_id_ = 0;
|
||||
|
||||
// Which host-activity has focus at the moment (Players talking to it, etc).
|
||||
Object::WeakRef<HostActivity> foreground_host_activity_;
|
||||
std::vector<Object::Ref<HostActivity> > host_activities_;
|
||||
PythonRef session_py_obj_;
|
||||
bool kick_idle_players_ = false;
|
||||
millisecs_t last_kick_idle_players_decrement_time_;
|
||||
millisecs_t next_prune_time_ = 0;
|
||||
std::map<std::string, Object::WeakRef<Texture> > textures_;
|
||||
std::map<std::string, Object::WeakRef<Sound> > sounds_;
|
||||
std::map<std::string, Object::WeakRef<Data> > datas_;
|
||||
std::map<std::string, Object::WeakRef<Model> > models_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SESSION_HOST_SESSION_H_
|
||||
147
src/ballistica/game/session/net_client_session.cc
Normal file
147
src/ballistica/game/session/net_client_session.cc
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/session/net_client_session.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/connection/connection_to_host.h"
|
||||
#include "ballistica/media/media_server.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
NetClientSession::NetClientSession() {
|
||||
// Sanity check: we should only ever be writing one replay at once.
|
||||
if (g_app_globals->replay_open) {
|
||||
Log("ERROR: g_replay_open true at netclient start; shouldn't happen.");
|
||||
}
|
||||
assert(g_media_server);
|
||||
g_media_server->PushBeginWriteReplayCall();
|
||||
writing_replay_ = true;
|
||||
g_app_globals->replay_open = true;
|
||||
}
|
||||
|
||||
NetClientSession::~NetClientSession() {
|
||||
if (writing_replay_) {
|
||||
// Sanity check: we should only ever be writing one replay at once.
|
||||
if (!g_app_globals->replay_open) {
|
||||
Log("ERROR: g_replay_open false at net-client close; shouldn't happen.");
|
||||
}
|
||||
g_app_globals->replay_open = false;
|
||||
assert(g_media_server);
|
||||
g_media_server->PushEndWriteReplayCall();
|
||||
writing_replay_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NetClientSession::SetConnectionToHost(ConnectionToHost* c) {
|
||||
connection_to_host_ = c;
|
||||
}
|
||||
|
||||
void NetClientSession::OnCommandBufferUnderrun() {
|
||||
// Any time we run out of data, hit the brakes on our playback speed.
|
||||
// Update: maybe not.
|
||||
// correction_ *= 0.99f;
|
||||
}
|
||||
|
||||
void NetClientSession::Update(int time_advance) {
|
||||
if (shutting_down_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now do standard step.
|
||||
ClientSession::Update(time_advance);
|
||||
|
||||
// And update our timing to try and ensure we don't run out of buffer.
|
||||
UpdateBuffering();
|
||||
}
|
||||
|
||||
void NetClientSession::UpdateBuffering() {
|
||||
// if (NetGraph *graph = g_graphics->debug_graph_1()) {
|
||||
// graph->addSample(GetRealTime(), steps_on_list_);
|
||||
// }
|
||||
|
||||
// Keep record of the most and least amount of time we've had buffered
|
||||
// recently, and slow down/speed up a bit based on that.
|
||||
{
|
||||
int bucket_count = static_cast<int>(least_buffered_count_list_.size());
|
||||
|
||||
// Change bucket every g_delay_samples samples.
|
||||
int bucket = (buffer_count_list_index_ / g_app_globals->delay_samples)
|
||||
% bucket_count;
|
||||
int bucket_iteration =
|
||||
buffer_count_list_index_ % g_app_globals->delay_samples;
|
||||
|
||||
// *Set* the value the first iteration in each bucket; do *min* after that.
|
||||
if (bucket_iteration == 0) {
|
||||
least_buffered_count_list_[bucket] = steps_on_list_;
|
||||
most_buffered_count_list_[bucket] = steps_on_list_;
|
||||
} else {
|
||||
least_buffered_count_list_[bucket] =
|
||||
std::min(least_buffered_count_list_[bucket], steps_on_list_);
|
||||
most_buffered_count_list_[bucket] =
|
||||
std::max(most_buffered_count_list_[bucket], steps_on_list_);
|
||||
|
||||
// After the last sample in each bucket, feed the max bucket value in
|
||||
// as the 'low pass' buffer-count. The low-pass curve minus our largest
|
||||
// spike value should be where we want to aim for in the buffer.
|
||||
if (bucket_iteration == g_app_globals->delay_samples - 1) {
|
||||
float smoothing = 0.5f;
|
||||
low_pass_smoothed_ =
|
||||
smoothing * low_pass_smoothed_
|
||||
+ (1.0f - smoothing)
|
||||
* static_cast<float>(most_buffered_count_list_[bucket]);
|
||||
}
|
||||
}
|
||||
|
||||
// Keep track of the largest min/max difference in our sample segments.
|
||||
int largest_spike = 0;
|
||||
|
||||
buffer_count_list_index_++;
|
||||
for (int i = 1; i < bucket_count; i++) {
|
||||
int spike = most_buffered_count_list_[i] - least_buffered_count_list_[i];
|
||||
if (spike > largest_spike) {
|
||||
largest_spike = spike;
|
||||
}
|
||||
}
|
||||
|
||||
// Slowly adjust largest spike value based on the biggest in recent history.
|
||||
{
|
||||
float smoothing = 0.95f;
|
||||
largest_spike_smoothed_ =
|
||||
smoothing * largest_spike_smoothed_
|
||||
+ (1.0f - smoothing) * static_cast<float>(largest_spike);
|
||||
}
|
||||
|
||||
// Low pass is the most buffered data we've had in the most recent slot.
|
||||
float ideal_offset = low_pass_smoothed_ - largest_spike_smoothed_ * 1.0f;
|
||||
|
||||
// Any time we've got no current buffered data, slow down fast.
|
||||
// (otherwise we can get stuck cruising along with no 0 buffered data and
|
||||
// things get real jerky looking)
|
||||
if (steps_on_list_ == 0) {
|
||||
ideal_offset -= 100.0f;
|
||||
}
|
||||
float smoothing = 0.0f;
|
||||
correction_ = smoothing * correction_
|
||||
+ (1.0f - smoothing) * (1.0f + 0.002f * ideal_offset);
|
||||
correction_ = std::min(1.5f, std::max(0.5f, correction_));
|
||||
// if (NetGraph *graph = g_graphics->debug_graph_2()) {
|
||||
// graph->addSample(GetRealTime(), correction_);
|
||||
// }
|
||||
}
|
||||
}
|
||||
void NetClientSession::HandleSessionMessage(
|
||||
const std::vector<uint8_t>& message) {
|
||||
// Do the standard thing, but also write this message straight to our replay
|
||||
// stream if we have one.
|
||||
ClientSession::HandleSessionMessage(message);
|
||||
|
||||
if (writing_replay_) {
|
||||
assert(g_media_server);
|
||||
g_media_server->PushAddMessageToReplayCall(message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
35
src/ballistica/game/session/net_client_session.h
Normal file
35
src/ballistica/game/session/net_client_session.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SESSION_NET_CLIENT_SESSION_H_
|
||||
#define BALLISTICA_GAME_SESSION_NET_CLIENT_SESSION_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/game/session/client_session.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A client-session fed by a connection to a host.
|
||||
class NetClientSession : public ClientSession {
|
||||
public:
|
||||
NetClientSession();
|
||||
~NetClientSession() override;
|
||||
auto connection_to_host() const -> ConnectionToHost* {
|
||||
return connection_to_host_.get();
|
||||
}
|
||||
void SetConnectionToHost(ConnectionToHost* c);
|
||||
void HandleSessionMessage(const std::vector<uint8_t>& buffer) override;
|
||||
void OnCommandBufferUnderrun() override;
|
||||
|
||||
protected:
|
||||
void Update(int time_advance) override;
|
||||
|
||||
private:
|
||||
void UpdateBuffering();
|
||||
bool writing_replay_ = false;
|
||||
Object::WeakRef<ConnectionToHost> connection_to_host_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SESSION_NET_CLIENT_SESSION_H_
|
||||
321
src/ballistica/game/session/replay_client_session.cc
Normal file
321
src/ballistica/game/session/replay_client_session.cc
Normal file
@ -0,0 +1,321 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/session/replay_client_session.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/dynamics/material/material.h"
|
||||
#include "ballistica/game/connection/connection_to_client.h"
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/generic/huffman.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/math/vector3f.h"
|
||||
#include "ballistica/networking/networking.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto ReplayClientSession::GetActualTimeAdvance(int advance_in) -> int {
|
||||
return static_cast<int>(
|
||||
round(advance_in * pow(2.0f, g_game->replay_speed_exponent())));
|
||||
}
|
||||
|
||||
ReplayClientSession::ReplayClientSession(std::string filename)
|
||||
: file_name_(std::move(filename)),
|
||||
file_(nullptr),
|
||||
message_fetch_num_(0),
|
||||
have_sent_client_message_(false) {
|
||||
// take responsibility for feeding all clients to this device..
|
||||
g_game->RegisterClientController(this);
|
||||
|
||||
// go ahead and just do a reset here, which will get things going..
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
ReplayClientSession::~ReplayClientSession() {
|
||||
// we no longer are responsible for feeding clients to this device..
|
||||
g_game->UnregisterClientController(this);
|
||||
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnClientConnected(ConnectionToClient* c) {
|
||||
// sanity check - abort if its on either of our lists already
|
||||
for (ConnectionToClient* i : connections_to_clients_) {
|
||||
if (i == c) {
|
||||
Log("Error: ReplayClientSession::OnClientConnected()"
|
||||
" got duplicate connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (ConnectionToClient* i : connections_to_clients_ignored_) {
|
||||
if (i == c) {
|
||||
Log("Error: ReplayClientSession::OnClientConnected()"
|
||||
" got duplicate connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we've sent *any* commands out to clients so far, we currently have to
|
||||
// ignore new connections (need to rebuild state to match current session
|
||||
// state)
|
||||
{
|
||||
connections_to_clients_.push_back(c);
|
||||
|
||||
// we create a temporary output stream just for the purpose of building
|
||||
// a giant session-commands message that we can send to the client
|
||||
// to build its state up to where we are currently.
|
||||
GameStream out(nullptr, false);
|
||||
|
||||
// go ahead and dump our full state..
|
||||
DumpFullState(&out);
|
||||
|
||||
// grab the message that's been built up..
|
||||
// if its not empty, send it to the client.
|
||||
std::vector<uint8_t> out_message = out.GetOutMessage();
|
||||
if (!out_message.empty()) c->SendReliableMessage(out_message);
|
||||
|
||||
// also send a correction packet to sync up all our dynamics
|
||||
// (technically could do this *just* for the new client)
|
||||
{
|
||||
std::vector<std::vector<uint8_t> > messages;
|
||||
bool blend = false;
|
||||
GetCorrectionMessages(blend, &messages);
|
||||
|
||||
// FIXME - have to send reliably at the moment since these will most
|
||||
// likely be bigger than our unreliable packet limit.. :-(
|
||||
for (auto&& i : messages) {
|
||||
for (auto&& j : connections_to_clients_) {
|
||||
j->SendReliableMessage(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnClientDisconnected(ConnectionToClient* c) {
|
||||
// search for it on either our ignored or regular lists..
|
||||
for (auto i = connections_to_clients_.begin();
|
||||
i != connections_to_clients_.end(); i++) {
|
||||
if (*i == c) {
|
||||
connections_to_clients_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto i = connections_to_clients_ignored_.begin();
|
||||
i != connections_to_clients_ignored_.end(); i++) {
|
||||
if (*i == c) {
|
||||
connections_to_clients_ignored_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log("Error: ReplayClientSession::OnClientDisconnected()"
|
||||
" called for connection not on lists");
|
||||
}
|
||||
|
||||
void ReplayClientSession::FetchMessages() {
|
||||
if (!file_ || shutting_down()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have no messages left, read from the file until we get some.
|
||||
while (commands_.empty()) {
|
||||
std::vector<uint8_t> buffer;
|
||||
uint8_t len8;
|
||||
uint32_t len32;
|
||||
|
||||
// read the size of the message..
|
||||
// the first byte represents the actual size if the value is < 254
|
||||
// if it is 254, the 2 bytes after it represent size
|
||||
// if it is 255, the 4 bytes after it represent size
|
||||
if (fread(&len8, 1, 1, file_) != 1) {
|
||||
// so they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
commands_.emplace_back(1,
|
||||
static_cast<uint8_t>(SessionCommand::kEndOfFile));
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if (len8 < 254) {
|
||||
len32 = len8;
|
||||
} else {
|
||||
// pull 16 bit len..
|
||||
if (len8 == 254) {
|
||||
uint16_t len16;
|
||||
if (fread(&len16, 2, 1, file_) != 1) {
|
||||
// so they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
commands_.emplace_back(
|
||||
1, static_cast<uint8_t>(SessionCommand::kEndOfFile));
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
assert(len16 >= 254);
|
||||
len32 = len16;
|
||||
} else {
|
||||
// pull 32 bit len...
|
||||
if (fread(&len32, 4, 1, file_) != 1) {
|
||||
// so they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
commands_.emplace_back(
|
||||
1, static_cast<uint8_t>(SessionCommand::kEndOfFile));
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
assert(len32 > 65535);
|
||||
}
|
||||
}
|
||||
|
||||
// read and decompress the actual message..
|
||||
BA_PRECONDITION(len32 > 0);
|
||||
buffer.resize(len32);
|
||||
if (fread(&(buffer[0]), len32, 1, file_) != 1) {
|
||||
commands_.emplace_back(1,
|
||||
static_cast<uint8_t>(SessionCommand::kEndOfFile));
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> data_decompressed =
|
||||
g_utils->huffman()->decompress(buffer);
|
||||
HandleSessionMessage(data_decompressed);
|
||||
|
||||
// Also send it to all client-connections we're attached to.
|
||||
// NOTE: We currently are sending everything as reliable; we can maybe do
|
||||
// unreliable for certain type of messages. Though perhaps when passing
|
||||
// around replays maybe its best to keep everything intact.
|
||||
have_sent_client_message_ = true;
|
||||
for (auto&& i : connections_to_clients_) {
|
||||
i->SendReliableMessage(data_decompressed);
|
||||
}
|
||||
message_fetch_num_++;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::Error(const std::string& description) {
|
||||
// Close the replay, announce something went wrong with it, and then do
|
||||
// standard error response..
|
||||
ScreenMessage(g_game->GetResourceString("replayReadErrorText"), {1, 0, 0});
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
ClientSession::Error(description);
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnReset(bool rewind) {
|
||||
// Handles base resetting.
|
||||
ClientSession::OnReset(rewind);
|
||||
|
||||
// If we've got any clients attached to us, tell them to reset as well.
|
||||
for (auto&& i : connections_to_clients_) {
|
||||
i->SendReliableMessage(std::vector<uint8_t>(1, BA_MESSAGE_SESSION_RESET));
|
||||
}
|
||||
|
||||
// If rewinding, pop back to the start of our file.
|
||||
if (rewind) {
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
|
||||
file_ = g_platform->FOpen(file_name_.c_str(), "rb");
|
||||
if (!file_) {
|
||||
Error("can't open file for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file ID and version to make sure we support this file.
|
||||
uint32_t file_id;
|
||||
if ((fread(&file_id, sizeof(file_id), 1, file_) != 1)) {
|
||||
Error("error reading file_id");
|
||||
return;
|
||||
}
|
||||
if (file_id != kBrpFileID) {
|
||||
Error("incorrect file_id");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure its a compatible protocol version.
|
||||
uint16_t version;
|
||||
if (fread(&version, sizeof(version), 1, file_) != 1) {
|
||||
Error("error reading version");
|
||||
return;
|
||||
}
|
||||
if (version > kProtocolVersion || version < kProtocolVersionMin) {
|
||||
ScreenMessage(g_game->GetResourceString("replayVersionErrorText"),
|
||||
{1, 0, 0});
|
||||
End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::DumpFullState(GameStream* out) {
|
||||
// This shouldn't actually be replay-specific. Should move this up to
|
||||
// ClientSession perhaps?
|
||||
|
||||
// Add all scenes.
|
||||
for (auto&& i : scenes_) {
|
||||
if (Scene* sg = i.get()) {
|
||||
sg->Dump(out);
|
||||
}
|
||||
}
|
||||
|
||||
// Before doing any nodes, we need to create all materials.
|
||||
// (but *not* their components, which may reference the nodes that we haven't
|
||||
// made yet)
|
||||
for (auto&& i : materials_) {
|
||||
if (Material* m = i.get()) {
|
||||
out->AddMaterial(m);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all media.
|
||||
for (auto&& i : textures_) {
|
||||
if (Texture* t = i.get()) {
|
||||
out->AddTexture(t);
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (Model* s = i.get()) {
|
||||
out->AddModel(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (Sound* s = i.get()) {
|
||||
out->AddSound(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : collide_models_) {
|
||||
if (CollideModel* s = i.get()) {
|
||||
out->AddCollideModel(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all scene nodes.
|
||||
for (auto&& i : scenes_) {
|
||||
if (Scene* sg = i.get()) {
|
||||
sg->DumpNodes(out);
|
||||
}
|
||||
}
|
||||
|
||||
// Now fill out materials since all the nodes/etc they reference exist.
|
||||
for (auto&& i : materials_) {
|
||||
if (Material* m = i.get()) {
|
||||
m->DumpComponents(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
43
src/ballistica/game/session/replay_client_session.h
Normal file
43
src/ballistica/game/session/replay_client_session.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SESSION_REPLAY_CLIENT_SESSION_H_
|
||||
#define BALLISTICA_GAME_SESSION_REPLAY_CLIENT_SESSION_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/game/client_controller_interface.h"
|
||||
#include "ballistica/game/session/client_session.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A client-session fed by a connection to a host.
|
||||
class ReplayClientSession : public ClientSession,
|
||||
public ClientControllerInterface {
|
||||
public:
|
||||
explicit ReplayClientSession(std::string filename);
|
||||
~ReplayClientSession() override;
|
||||
void OnReset(bool rewind) override;
|
||||
|
||||
// Our ClientControllerInterface implementation.
|
||||
auto GetActualTimeAdvance(int advance_in) -> int override;
|
||||
void OnClientConnected(ConnectionToClient* c) override;
|
||||
void OnClientDisconnected(ConnectionToClient* c) override;
|
||||
void DumpFullState(GameStream* out) override;
|
||||
|
||||
protected:
|
||||
void Error(const std::string& description) override;
|
||||
void FetchMessages() override;
|
||||
|
||||
private:
|
||||
uint32_t message_fetch_num_;
|
||||
bool have_sent_client_message_;
|
||||
std::vector<ConnectionToClient*> connections_to_clients_;
|
||||
std::vector<ConnectionToClient*> connections_to_clients_ignored_;
|
||||
std::string file_name_;
|
||||
FILE* file_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SESSION_REPLAY_CLIENT_SESSION_H_
|
||||
36
src/ballistica/game/session/session.cc
Normal file
36
src/ballistica/game/session/session.cc
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/game/session/session.h"
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/game.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
Session::Session() {
|
||||
g_app_globals->session_count++;
|
||||
// new sessions immediately become foreground
|
||||
g_game->SetForegroundSession(this);
|
||||
}
|
||||
|
||||
Session::~Session() { g_app_globals->session_count--; }
|
||||
|
||||
void Session::Update(int time_advance) {}
|
||||
|
||||
auto Session::GetForegroundContext() -> Context { return Context(); }
|
||||
|
||||
void Session::Draw(FrameDef*) {}
|
||||
|
||||
void Session::ScreenSizeChanged() {}
|
||||
|
||||
void Session::LanguageChanged() {}
|
||||
|
||||
void Session::GraphicsQualityChanged(GraphicsQuality q) {}
|
||||
|
||||
void Session::DebugSpeedMultChanged() {}
|
||||
|
||||
void Session::DumpFullState(GameStream* out) {
|
||||
Log("Session::DumpFullState() being called; shouldn't happen.");
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
44
src/ballistica/game/session/session.h
Normal file
44
src/ballistica/game/session/session.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GAME_SESSION_SESSION_H_
|
||||
#define BALLISTICA_GAME_SESSION_SESSION_H_
|
||||
|
||||
#include "ballistica/core/context.h"
|
||||
#include "ballistica/core/object.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class Session : public ContextTarget {
|
||||
public:
|
||||
Session();
|
||||
~Session() override;
|
||||
|
||||
// Update the session. Should return real milliseconds until next
|
||||
// update is needed.
|
||||
virtual void Update(int time_advance);
|
||||
|
||||
// If this returns false, the screen will be cleared as part of rendering.
|
||||
virtual auto DoesFillScreen() const -> bool = 0;
|
||||
|
||||
// Draw!!!
|
||||
virtual void Draw(FrameDef* f);
|
||||
|
||||
// Return the 'frontmost' context in the session.
|
||||
// This is used for executing console command or other UI hotkeys that should
|
||||
// apply to whatever the user is seeing.
|
||||
virtual auto GetForegroundContext() -> Context;
|
||||
virtual void ScreenSizeChanged();
|
||||
virtual void LanguageChanged();
|
||||
virtual void GraphicsQualityChanged(GraphicsQuality q);
|
||||
virtual void DebugSpeedMultChanged();
|
||||
auto benchmark_type() const -> BenchmarkType { return benchmark_type_; }
|
||||
void set_benchmark_type(BenchmarkType val) { benchmark_type_ = val; }
|
||||
virtual void DumpFullState(GameStream* s);
|
||||
|
||||
private:
|
||||
BenchmarkType benchmark_type_ = BenchmarkType::kNone;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GAME_SESSION_SESSION_H_
|
||||
19
src/ballistica/graphics/area_of_interest.cc
Normal file
19
src/ballistica/graphics/area_of_interest.cc
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/area_of_interest.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
AreaOfInterest::AreaOfInterest(bool in_focus) : in_focus_(in_focus) {}
|
||||
|
||||
AreaOfInterest::~AreaOfInterest() = default;
|
||||
|
||||
void AreaOfInterest::SetRadius(float r_in) {
|
||||
// We slightly scale this for phone situations.
|
||||
float extrascale = (GetInterfaceType() == UIScale::kSmall) ? 0.85f : 1.0f;
|
||||
radius_ = r_in * extrascale;
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
33
src/ballistica/graphics/area_of_interest.h
Normal file
33
src/ballistica/graphics/area_of_interest.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_AREA_OF_INTEREST_H_
|
||||
#define BALLISTICA_GRAPHICS_AREA_OF_INTEREST_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/math/vector3f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class AreaOfInterest {
|
||||
public:
|
||||
explicit AreaOfInterest(bool in_focus);
|
||||
~AreaOfInterest();
|
||||
void set_position(const Vector3f& position) { position_ = position; }
|
||||
void set_velocity(const Vector3f& velocity) { velocity_ = velocity; }
|
||||
auto position() const -> const Vector3f& { return position_; }
|
||||
auto velocity() const -> const Vector3f& { return velocity_; }
|
||||
void SetRadius(float r);
|
||||
auto in_focus() const -> bool { return in_focus_; }
|
||||
auto radius() const -> float { return radius_; }
|
||||
|
||||
private:
|
||||
Vector3f position_ = {0.0f, 0.0f, 0.0f};
|
||||
Vector3f velocity_ = {0.0f, 0.0f, 0.0f};
|
||||
float radius_ = 1.0f;
|
||||
bool in_focus_ = false;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_AREA_OF_INTEREST_H_
|
||||
1016
src/ballistica/graphics/camera.cc
Normal file
1016
src/ballistica/graphics/camera.cc
Normal file
File diff suppressed because it is too large
Load Diff
152
src/ballistica/graphics/camera.h
Normal file
152
src/ballistica/graphics/camera.h
Normal file
@ -0,0 +1,152 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_CAMERA_H_
|
||||
#define BALLISTICA_GRAPHICS_CAMERA_H_
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/math/vector3f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Hmm this shouldn't be here.
|
||||
const float kHappyThoughtsZPlane = -5.52f;
|
||||
|
||||
// Default horizontal camera field of view.
|
||||
const float kCameraFOVY = 60.0f;
|
||||
const float kInitialHeading = -1.0f;
|
||||
|
||||
// FIXME: looks like this guy gets accessed from a few different threads.
|
||||
class Camera : public Object {
|
||||
public:
|
||||
Camera();
|
||||
~Camera() override;
|
||||
void Shake(float amount);
|
||||
void SetManual(bool enable);
|
||||
auto manual() const -> bool { return manual_; }
|
||||
void ManualHandleMouseMove(float move_h, float move_v);
|
||||
void ManualHandleMouseWheel(float val);
|
||||
|
||||
// Update the camera position values - done once per render
|
||||
void UpdatePosition();
|
||||
|
||||
// Update camera velocities/etc - done as often as possible.
|
||||
void Update(millisecs_t elapsed);
|
||||
void SetPosition(float x, float y, float z);
|
||||
void SetTarget(float x, float y, float z);
|
||||
void SetMode(CameraMode m);
|
||||
void set_area_of_interest_bounds(float min_x, float min_y, float min_z,
|
||||
float max_x, float max_y, float max_z) {
|
||||
area_of_interest_bounds_[0] = min_x;
|
||||
area_of_interest_bounds_[1] = min_y;
|
||||
area_of_interest_bounds_[2] = min_z;
|
||||
area_of_interest_bounds_[3] = max_x;
|
||||
area_of_interest_bounds_[4] = max_y;
|
||||
area_of_interest_bounds_[5] = max_z;
|
||||
}
|
||||
void area_of_interest_bounds(float* min_x, float* min_y, float* min_z,
|
||||
float* max_x, float* max_y, float* max_z) {
|
||||
*min_x = area_of_interest_bounds_[0];
|
||||
*min_y = area_of_interest_bounds_[1];
|
||||
*min_z = area_of_interest_bounds_[2];
|
||||
*max_x = area_of_interest_bounds_[3];
|
||||
*max_y = area_of_interest_bounds_[4];
|
||||
*max_z = area_of_interest_bounds_[5];
|
||||
}
|
||||
void UpdateManualMode();
|
||||
|
||||
// Sets up the render in the passes we're associated with. Call this anytime
|
||||
// during a render.
|
||||
void ApplyToFrameDef(FrameDef* frame_def);
|
||||
auto field_of_view_y() const -> float { return field_of_view_y_; }
|
||||
void get_position(float* x, float* y, float* z) const {
|
||||
*x = position_.x;
|
||||
*y = position_.y;
|
||||
*z = position_.z;
|
||||
}
|
||||
void target_smoothed(float* x, float* y, float* z) const {
|
||||
*x = target_smoothed_.x;
|
||||
*y = target_smoothed_.y;
|
||||
*z = target_smoothed_.z;
|
||||
}
|
||||
void set_alt_down(bool d) { alt_down_ = d; }
|
||||
void set_cmd_down(bool d) { cmd_down_ = d; }
|
||||
void set_ctrl_down(bool d) { ctrl_down_ = d; }
|
||||
void set_mouse_left_down(bool d) { mouse_left_down_ = d; }
|
||||
void set_mouse_right_down(bool d) { mouse_right_down_ = d; }
|
||||
void set_mouse_middle_down(bool d) { mouse_middle_down_ = d; }
|
||||
void set_happy_thoughts_mode(bool h) { happy_thoughts_mode_ = h; }
|
||||
auto happy_thoughts_mode() const -> bool { return happy_thoughts_mode_; }
|
||||
auto NewAreaOfInterest(bool inFocus = true) -> AreaOfInterest*;
|
||||
void DeleteAreaOfInterest(AreaOfInterest* a);
|
||||
auto mode() const -> CameraMode { return mode_; }
|
||||
void set_vr_offset(const Vector3f& val) { vr_offset_ = val; }
|
||||
void set_vr_extra_offset(const Vector3f& val) { vr_extra_offset_ = val; }
|
||||
auto vr_extra_offset() const -> const Vector3f& { return vr_extra_offset_; }
|
||||
void set_lock_panning(bool val) { lock_panning_ = val; }
|
||||
auto lock_panning() const -> bool { return lock_panning_; }
|
||||
auto pan_speed_scale() const -> float { return pan_speed_scale_; }
|
||||
void set_pan_speed_scale(float val) { pan_speed_scale_ = val; }
|
||||
|
||||
private:
|
||||
float pan_speed_scale_{1.0f};
|
||||
bool lock_panning_{};
|
||||
Vector3f vr_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_extra_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vr_offset_smooth_{0.0f, 0.0f, 0.0f};
|
||||
millisecs_t last_mode_set_time_{};
|
||||
std::list<AreaOfInterest> areas_of_interest_;
|
||||
CameraMode mode_{CameraMode::kFollow};
|
||||
bool manual_{};
|
||||
bool smooth_next_frame_{};
|
||||
bool have_real_areas_of_interest_{};
|
||||
|
||||
// Manual stuff.
|
||||
bool panning_{};
|
||||
bool orbiting_{};
|
||||
bool rolling_{};
|
||||
bool trucking_{};
|
||||
bool alt_down_{};
|
||||
bool cmd_down_{};
|
||||
bool ctrl_down_{};
|
||||
bool mouse_left_down_{};
|
||||
bool mouse_middle_down_{};
|
||||
bool mouse_right_down_{};
|
||||
float heading_{kInitialHeading};
|
||||
Vector3f extra_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f extra_pos_2_{0.0f, 0.0f, 0.0f};
|
||||
float area_of_interest_bounds_[6]{-9999, -9999, -9999, 9999, 9999, 9999};
|
||||
float pan_pos_{};
|
||||
float pan_speed_{};
|
||||
float pan_target_{};
|
||||
float shake_amount_{};
|
||||
Vector3f shake_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f shake_vel_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f position_{0.0f, 1.0f, -1.0f};
|
||||
Vector3f target_{0.0f, 1.0f, -1.0f};
|
||||
Vector3f target_smoothed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f position_offset_smoothed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f smooth_speed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f smooth_speed_2_{0.0f, 0.0f, 0.0f};
|
||||
float target_radius_{2.0f};
|
||||
float target_radius_smoothed_{2.0f};
|
||||
float field_of_view_x_{5.0f};
|
||||
float field_of_view_y_{kCameraFOVY};
|
||||
float field_of_view_x_smoothed_{1.0f};
|
||||
float field_of_view_y_smoothed_{1.0f};
|
||||
millisecs_t last_listener_update_time_{};
|
||||
bool happy_thoughts_mode_{};
|
||||
float min_target_radius_{5.0f};
|
||||
Vector3f up_{0.0f, 1.0f, 0.0f};
|
||||
float area_of_interest_near_{1.0f};
|
||||
float area_of_interest_far_{2.0f};
|
||||
bool x_constrained_{true};
|
||||
float xy_constrain_blend_{0.5f};
|
||||
std::vector<Vector3f> area_of_interest_points_{{0.0f, 0.0f, 0.0f}};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_CAMERA_H_
|
||||
30
src/ballistica/graphics/component/empty_component.h
Normal file
30
src/ballistica/graphics/component/empty_component.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_EMPTY_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_EMPTY_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Empty component - has no shader but can be useful for spitting out
|
||||
// transform/scissor/etc state changes.
|
||||
class EmptyComponent : public RenderComponent {
|
||||
public:
|
||||
explicit EmptyComponent(RenderPass* pass)
|
||||
: RenderComponent(pass), transparent_(false) {}
|
||||
void SetTransparent(bool val) {
|
||||
EnsureConfiguring();
|
||||
transparent_ = val;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override { ConfigForEmpty(transparent_); }
|
||||
|
||||
private:
|
||||
bool transparent_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_EMPTY_COMPONENT_H_
|
||||
195
src/ballistica/graphics/component/object_component.cc
Normal file
195
src/ballistica/graphics/component/object_component.cc
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/object_component.h"
|
||||
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void ObjectComponent::WriteConfig() {
|
||||
// If they didn't give us a texture, just use a blank white texture.
|
||||
// This is not a common case and easier than forking all our shaders to
|
||||
// create non-textured versions.
|
||||
if (!texture_.exists()) {
|
||||
texture_ = g_media->GetTexture(SystemTextureID::kWhite);
|
||||
}
|
||||
if (reflection_ == ReflectionType::kNone) {
|
||||
assert(!double_sided_); // Unsupported combo.
|
||||
assert(!colorize_texture_.exists()); // Unsupported combo.
|
||||
assert(!have_color_add_); // Unsupported combo.
|
||||
if (light_shadow_ == LightShadowType::kNone) {
|
||||
if (transparent_) {
|
||||
ConfigForShading(ShadingType::kObjectTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObject);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
}
|
||||
} else {
|
||||
if (transparent_) {
|
||||
assert(!world_space_); // Unsupported combo.
|
||||
ConfigForShading(ShadingType::kObjectLightShadowTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectLightShadow);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutInt(world_space_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (light_shadow_ == LightShadowType::kNone) {
|
||||
assert(!double_sided_); // Unsupported combo.
|
||||
assert(!colorize_texture_.exists()); // Unsupported combo.
|
||||
if (transparent_) {
|
||||
assert(!world_space_); // Unsupported combo.
|
||||
if (have_color_add_) {
|
||||
ConfigForShading(ShadingType::kObjectReflectAddTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
color_add_r_, color_add_g_, color_add_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectReflectTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectReflect);
|
||||
cmd_buffer_->PutInt(world_space_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
} else {
|
||||
// With add.
|
||||
assert(!transparent_); // Unsupported combo.
|
||||
if (!have_color_add_) {
|
||||
if (colorize_texture_.exists()) {
|
||||
assert(!double_sided_); // Unsupported combo.
|
||||
assert(!world_space_); // Unsupported combo.
|
||||
if (do_colorize_2_) {
|
||||
ConfigForShading(ShadingType::kObjectReflectLightShadowColorized2);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(
|
||||
color_r_, color_g_, color_b_, reflection_scale_r_,
|
||||
reflection_scale_g_, reflection_scale_b_, colorize_color_r_,
|
||||
colorize_color_g_, colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectReflectLightShadowColorized);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_, colorize_color_r_,
|
||||
colorize_color_g_, colorize_color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
} else {
|
||||
if (double_sided_) {
|
||||
ConfigForShading(ShadingType::kObjectReflectLightShadowDoubleSided);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutInt(world_space_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectReflectLightShadow);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutInt(world_space_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(!double_sided_); // Unsupported combo.
|
||||
assert(!world_space_); // Unsupported config.
|
||||
if (colorize_texture_.exists()) {
|
||||
if (do_colorize_2_) {
|
||||
ConfigForShading(
|
||||
ShadingType::kObjectReflectLightShadowAddColorized2);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(
|
||||
color_r_, color_g_, color_b_, color_add_r_, color_add_g_,
|
||||
color_add_b_, reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_, colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_, colorize_color2_r_, colorize_color2_g_,
|
||||
colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
} else {
|
||||
ConfigForShading(
|
||||
ShadingType::kObjectReflectLightShadowAddColorized);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_add_r_,
|
||||
color_add_g_, color_add_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_, colorize_color_r_,
|
||||
colorize_color_g_, colorize_color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kObjectReflectLightShadowAdd);
|
||||
cmd_buffer_->PutInt(static_cast<int>(light_shadow_));
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_add_r_,
|
||||
color_add_g_, color_add_b_,
|
||||
reflection_scale_r_, reflection_scale_g_,
|
||||
reflection_scale_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
SystemCubeMapTextureID r =
|
||||
Graphics::CubeMapFromReflectionType(reflection_);
|
||||
cmd_buffer_->PutCubeMapTexture(g_media->GetCubeMapTexture(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
166
src/ballistica/graphics/component/object_component.h
Normal file
166
src/ballistica/graphics/component/object_component.h
Normal file
@ -0,0 +1,166 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_OBJECT_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_OBJECT_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class ObjectComponent : public RenderComponent {
|
||||
public:
|
||||
explicit ObjectComponent(RenderPass* pass) : RenderComponent(pass) {}
|
||||
|
||||
void SetTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
texture_ = t_in->texture_data();
|
||||
} else {
|
||||
texture_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SetTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
texture_ = t;
|
||||
}
|
||||
|
||||
void SetColorizeTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
colorize_texture_ = t_in->texture_data();
|
||||
} else {
|
||||
colorize_texture_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SetColorizeTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
colorize_texture_ = t;
|
||||
}
|
||||
|
||||
void SetDoubleSided(bool enable) {
|
||||
EnsureConfiguring();
|
||||
double_sided_ = enable;
|
||||
}
|
||||
|
||||
void SetReflection(ReflectionType r) {
|
||||
EnsureConfiguring();
|
||||
reflection_ = r;
|
||||
}
|
||||
|
||||
void SetReflectionScale(float r, float g, float b) {
|
||||
EnsureConfiguring();
|
||||
reflection_scale_r_ = r;
|
||||
reflection_scale_g_ = g;
|
||||
reflection_scale_b_ = b;
|
||||
}
|
||||
|
||||
void SetPremultiplied(bool val) {
|
||||
EnsureConfiguring();
|
||||
premultiplied_ = val;
|
||||
}
|
||||
|
||||
void SetTransparent(bool val) {
|
||||
EnsureConfiguring();
|
||||
transparent_ = val;
|
||||
}
|
||||
|
||||
void SetColor(float r, float g, float b, float a = 1.0f) {
|
||||
// We support fast inline color changes with drawing streams.
|
||||
// (avoids having to re-send a whole configure for every color change)
|
||||
if (state_ == State::kDrawing) {
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kObjectComponentInlineColor);
|
||||
cmd_buffer_->PutFloats(r, g, b, a);
|
||||
} else {
|
||||
EnsureConfiguring();
|
||||
}
|
||||
color_r_ = r;
|
||||
color_g_ = g;
|
||||
color_b_ = b;
|
||||
color_a_ = a;
|
||||
}
|
||||
|
||||
void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
colorize_color_r_ = r;
|
||||
colorize_color_g_ = g;
|
||||
colorize_color_b_ = b;
|
||||
colorize_color_a_ = a;
|
||||
}
|
||||
|
||||
void SetColorizeColor2(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
colorize_color2_r_ = r;
|
||||
colorize_color2_g_ = g;
|
||||
colorize_color2_b_ = b;
|
||||
colorize_color2_a_ = a;
|
||||
do_colorize_2_ = true;
|
||||
}
|
||||
|
||||
void SetAddColor(float r, float g, float b) {
|
||||
// We support fast inline add-color changes with drawing streams
|
||||
// (avoids having to re-send a whole configure for every change).
|
||||
// Make sure to only allow this if we have an add color already;
|
||||
// otherwise we need to config since we might be switching shaders.
|
||||
if (state_ == State::kDrawing && have_color_add_) {
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kObjectComponentInlineAddColor);
|
||||
cmd_buffer_->PutFloats(r, g, b);
|
||||
} else {
|
||||
EnsureConfiguring();
|
||||
}
|
||||
color_add_r_ = r;
|
||||
color_add_g_ = g;
|
||||
color_add_b_ = b;
|
||||
have_color_add_ = true;
|
||||
}
|
||||
|
||||
void SetLightShadow(LightShadowType t) {
|
||||
EnsureConfiguring();
|
||||
light_shadow_ = t;
|
||||
}
|
||||
|
||||
void SetWorldSpace(bool w) {
|
||||
EnsureConfiguring();
|
||||
world_space_ = w;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
|
||||
protected:
|
||||
float color_r_{1.0f};
|
||||
float color_g_{1.0f};
|
||||
float color_b_{1.0f};
|
||||
float color_a_{1.0f};
|
||||
float colorize_color_r_{1.0f};
|
||||
float colorize_color_g_{1.0f};
|
||||
float colorize_color_b_{1.0f};
|
||||
float colorize_color_a_{1.0f};
|
||||
float colorize_color2_r_{};
|
||||
float colorize_color2_g_{};
|
||||
float colorize_color2_b_{};
|
||||
float colorize_color2_a_{};
|
||||
float color_add_r_{};
|
||||
float color_add_g_{};
|
||||
float color_add_b_{};
|
||||
float reflection_scale_r_{1.0f};
|
||||
float reflection_scale_g_{1.0f};
|
||||
float reflection_scale_b_{1.0f};
|
||||
Object::Ref<TextureData> texture_;
|
||||
Object::Ref<TextureData> colorize_texture_;
|
||||
ReflectionType reflection_{ReflectionType::kNone};
|
||||
LightShadowType light_shadow_{LightShadowType::kObject};
|
||||
bool world_space_{};
|
||||
bool transparent_{};
|
||||
bool premultiplied_{};
|
||||
bool have_color_add_{};
|
||||
bool double_sided_{};
|
||||
bool do_colorize_2_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_OBJECT_COMPONENT_H_
|
||||
21
src/ballistica/graphics/component/post_process_component.cc
Normal file
21
src/ballistica/graphics/component/post_process_component.cc
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/post_process_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void PostProcessComponent::WriteConfig() {
|
||||
if (eyes_) {
|
||||
assert(normal_distort_ == 0.0f); // unsupported config
|
||||
ConfigForShading(ShadingType::kPostProcessEyes);
|
||||
} else {
|
||||
if (normal_distort_ != 0.0f) {
|
||||
ConfigForShading(ShadingType::kPostProcessNormalDistort);
|
||||
cmd_buffer_->PutFloat(normal_distort_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kPostProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
31
src/ballistica/graphics/component/post_process_component.h
Normal file
31
src/ballistica/graphics/component/post_process_component.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_POST_PROCESS_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_POST_PROCESS_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class PostProcessComponent : public RenderComponent {
|
||||
public:
|
||||
explicit PostProcessComponent(RenderPass* pass)
|
||||
: RenderComponent(pass), normal_distort_(0.0f), eyes_(false) {}
|
||||
void setNormalDistort(float d) {
|
||||
EnsureConfiguring();
|
||||
normal_distort_ = d;
|
||||
}
|
||||
void setEyes(bool enable) {
|
||||
EnsureConfiguring();
|
||||
eyes_ = enable;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
bool eyes_;
|
||||
float normal_distort_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_POST_PROCESS_COMPONENT_H_
|
||||
83
src/ballistica/graphics/component/render_component.cc
Normal file
83
src/ballistica/graphics/component/render_component.cc
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
#include "ballistica/dynamics/rigid_body.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void RenderComponent::ScissorPush(const Rect& rIn) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPush);
|
||||
cmd_buffer_->PutFloats(rIn.l, rIn.b, rIn.r, rIn.t);
|
||||
}
|
||||
|
||||
#if BA_DEBUG_BUILD
|
||||
void RenderComponent::ConfigForEmptyDebugChecks(bool transparent) {
|
||||
assert(InGameThread());
|
||||
if (g_graphics->drawing_opaque_only() && transparent) {
|
||||
throw Exception("Transparent component submitted in opaque-only section");
|
||||
}
|
||||
if (g_graphics->drawing_transparent_only() && !transparent) {
|
||||
throw Exception("Opaque component submitted in transparent-only section");
|
||||
}
|
||||
}
|
||||
|
||||
void RenderComponent::ConfigForShadingDebugChecks(ShadingType shading_type) {
|
||||
assert(InGameThread());
|
||||
if (g_graphics->drawing_opaque_only()
|
||||
&& Graphics::IsShaderTransparent(shading_type)) {
|
||||
throw Exception("Transparent component submitted in opaque-only section");
|
||||
}
|
||||
if (g_graphics->drawing_transparent_only()
|
||||
&& !Graphics::IsShaderTransparent(shading_type)) {
|
||||
throw Exception("Opaque component submitted in transparent-only section");
|
||||
}
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
|
||||
void RenderComponent::TransformToBody(const RigidBody& b) {
|
||||
dBodyID body = b.body();
|
||||
dGeomID geom = b.geom();
|
||||
const dReal* pos_in;
|
||||
const dReal* r_in;
|
||||
if (b.type() == RigidBody::Type::kBody) {
|
||||
pos_in = dBodyGetPosition(body);
|
||||
r_in = dBodyGetRotation(body);
|
||||
} else {
|
||||
pos_in = dGeomGetPosition(geom);
|
||||
r_in = dGeomGetRotation(geom);
|
||||
}
|
||||
float pos[3];
|
||||
float r[12];
|
||||
for (int x = 0; x < 3; x++) {
|
||||
pos[x] = pos_in[x];
|
||||
}
|
||||
pos[0] += b.blend_offset().x;
|
||||
pos[1] += b.blend_offset().y;
|
||||
pos[2] += b.blend_offset().z;
|
||||
for (int x = 0; x < 12; x++) {
|
||||
r[x] = r_in[x];
|
||||
}
|
||||
float matrix[16];
|
||||
matrix[0] = r[0];
|
||||
matrix[1] = r[4];
|
||||
matrix[2] = r[8];
|
||||
matrix[3] = 0;
|
||||
matrix[4] = r[1];
|
||||
matrix[5] = r[5];
|
||||
matrix[6] = r[9];
|
||||
matrix[7] = 0;
|
||||
matrix[8] = r[2];
|
||||
matrix[9] = r[6];
|
||||
matrix[10] = r[10];
|
||||
matrix[11] = 0;
|
||||
matrix[12] = pos[0];
|
||||
matrix[13] = pos[1];
|
||||
matrix[14] = pos[2];
|
||||
matrix[15] = 1;
|
||||
MultMatrix(matrix);
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
262
src/ballistica/graphics/component/render_component.h
Normal file
262
src/ballistica/graphics/component/render_component.h
Normal file
@ -0,0 +1,262 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class RenderComponent {
|
||||
public:
|
||||
explicit RenderComponent(RenderPass* pass)
|
||||
: state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {}
|
||||
~RenderComponent() {
|
||||
if (state_ != State::kSubmitted) {
|
||||
Log("Error: RenderComponent dying without submit() having been called.");
|
||||
}
|
||||
}
|
||||
void DrawModel(ModelData* model, uint32_t flags = 0) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawModel);
|
||||
cmd_buffer_->PutInt(flags);
|
||||
cmd_buffer_->PutModel(model);
|
||||
}
|
||||
void DrawModelInstanced(ModelData* model,
|
||||
const std::vector<Matrix44f>& matrices,
|
||||
int flags = 0) {
|
||||
assert(!matrices.empty());
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawModelInstanced);
|
||||
cmd_buffer_->PutInt(flags);
|
||||
cmd_buffer_->PutModel(model);
|
||||
cmd_buffer_->PutMatrices(matrices);
|
||||
}
|
||||
void DrawMesh(Mesh* m, int flags = 0) {
|
||||
EnsureDrawing();
|
||||
if (m->IsValid()) {
|
||||
cmd_buffer_->frame_def()->AddMesh(m);
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawMesh);
|
||||
cmd_buffer_->PutInt(flags);
|
||||
cmd_buffer_->PutMeshData(m->mesh_data_client_handle()->mesh_data);
|
||||
}
|
||||
}
|
||||
void DrawScreenQuad() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawScreenQuad);
|
||||
}
|
||||
// draw triangles using old-school gl format.. only for debugging
|
||||
// and not supported in all configurations
|
||||
void BeginDebugDrawTriangles() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kBeginDebugDrawTriangles);
|
||||
}
|
||||
void BeginDebugDrawLines() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kBeginDebugDrawLines);
|
||||
}
|
||||
void Vertex(float x, float y, float z) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDebugDrawVertex3);
|
||||
cmd_buffer_->PutFloats(x, y, z);
|
||||
}
|
||||
void End() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kEndDebugDraw);
|
||||
}
|
||||
void ScissorPush(const Rect& rIn);
|
||||
void ScissorPop() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPop);
|
||||
}
|
||||
void PushTransform() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPushTransform);
|
||||
}
|
||||
void PopTransform() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform);
|
||||
}
|
||||
void Translate(float x, float y) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2);
|
||||
cmd_buffer_->PutFloats(x, y);
|
||||
}
|
||||
void Translate(float x, float y, float z) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate3);
|
||||
cmd_buffer_->PutFloats(x, y, z);
|
||||
}
|
||||
void CursorTranslate() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kCursorTranslate);
|
||||
}
|
||||
void Rotate(float angle, float x, float y, float z) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kRotate);
|
||||
cmd_buffer_->PutFloats(angle, x, y, z);
|
||||
}
|
||||
void Scale(float x, float y) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale2);
|
||||
cmd_buffer_->PutFloats(x, y);
|
||||
}
|
||||
void Scale(float x, float y, float z) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale3);
|
||||
cmd_buffer_->PutFloats(x, y, z);
|
||||
}
|
||||
void ScaleUniform(float s) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScaleUniform);
|
||||
cmd_buffer_->PutFloat(s);
|
||||
}
|
||||
void MultMatrix(const float* t) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kMultMatrix);
|
||||
cmd_buffer_->PutFloatArray16(t);
|
||||
}
|
||||
void TransformToBody(const RigidBody& b);
|
||||
#if BA_VR_BUILD
|
||||
void VRTransformToRightHand() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kTransformToRightHand);
|
||||
}
|
||||
void VRTransformToLeftHand() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToLeftHand);
|
||||
}
|
||||
void VRTransformToHead() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToHead);
|
||||
}
|
||||
#endif // BA_VR_BUILD
|
||||
void TranslateToProjectedPoint(float x, float y, float z) {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kTranslateToProjectedPoint);
|
||||
cmd_buffer_->PutFloats(x, y, z);
|
||||
}
|
||||
void FlipCullFace() {
|
||||
EnsureDrawing();
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kFlipCullFace);
|
||||
}
|
||||
void Submit() {
|
||||
if (state_ != State::kSubmitted) {
|
||||
// if we were drawing, make note that we're done
|
||||
if (state_ == State::kDrawing) {
|
||||
#if BA_DEBUG_BUILD
|
||||
assert(pass_->frame_def()->defining_component());
|
||||
pass_->frame_def()->set_defining_component(false);
|
||||
#endif // BA_DEBUG_BUILD
|
||||
}
|
||||
state_ = State::kSubmitted;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
enum class State { kConfiguring, kDrawing, kSubmitted };
|
||||
void EnsureConfiguring() {
|
||||
if (state_ != State::kConfiguring) {
|
||||
// if we were drawing, make note that we're done
|
||||
#if BA_DEBUG_BUILD
|
||||
if (state_ == State::kDrawing) {
|
||||
assert(pass_->frame_def()->defining_component());
|
||||
pass_->frame_def()->set_defining_component(false);
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
state_ = State::kConfiguring;
|
||||
}
|
||||
}
|
||||
#if BA_DEBUG_BUILD
|
||||
void ConfigForEmptyDebugChecks(bool transparent);
|
||||
void ConfigForShadingDebugChecks(ShadingType shading_type);
|
||||
#endif
|
||||
|
||||
// Given a shader type, returns a buffer to write the command stream to.
|
||||
void ConfigForEmpty(bool transparent) {
|
||||
#if BA_DEBUG_BUILD
|
||||
ConfigForEmptyDebugChecks(transparent);
|
||||
#endif
|
||||
|
||||
assert(!pass_->UsesWorldLists());
|
||||
if (transparent) {
|
||||
cmd_buffer_ = pass_->commands_flat_transparent();
|
||||
} else {
|
||||
cmd_buffer_ = pass_->commands_flat();
|
||||
}
|
||||
}
|
||||
|
||||
// Given a shader type, sets up the config target buffer.
|
||||
void ConfigForShading(ShadingType shading_type) {
|
||||
// Determine which buffer to write to, etc.
|
||||
// Debugging: if we've got transparent-only or opaque-only mode flipped on,
|
||||
// make sure only those type of components are being submitted.
|
||||
#if BA_DEBUG_BUILD
|
||||
ConfigForShadingDebugChecks(shading_type);
|
||||
// Also make sure only transparent stuff is going into the
|
||||
// light/shadow/overlay3D passes (we skip rendering the opaque lists there
|
||||
// since there shouldn't be anything in them, and we're not using depth
|
||||
// for those so it wouldn't be much of an optimization..)
|
||||
if ((pass_->type() == RenderPass::Type::kLightPass
|
||||
|| pass_->type() == RenderPass::Type::kLightShadowPass
|
||||
|| pass_->type() == RenderPass::Type::kOverlay3DPass)
|
||||
&& !Graphics::IsShaderTransparent(shading_type)) {
|
||||
throw Exception(
|
||||
"Opaque component submitted to light/shadow/overlay3d pass;"
|
||||
" not cool man.");
|
||||
}
|
||||
|
||||
// Likewise the blit pass should consist solely of opaque stuff.
|
||||
if (pass_->type() == RenderPass::Type::kBlitPass
|
||||
&& Graphics::IsShaderTransparent(shading_type)) {
|
||||
throw Exception(
|
||||
"Transparent component submitted to blit pass;"
|
||||
" not cool man.");
|
||||
}
|
||||
#endif // BA_DEBUG_BUILD
|
||||
// Certain passes (overlay, etc) draw objects in the order
|
||||
// provided. Other passes group by shader for efficiency.
|
||||
if (pass_->UsesWorldLists()) {
|
||||
cmd_buffer_ = pass_->GetCommands(shading_type);
|
||||
} else {
|
||||
if (Graphics::IsShaderTransparent(shading_type)) {
|
||||
cmd_buffer_ = pass_->commands_flat_transparent();
|
||||
} else {
|
||||
cmd_buffer_ = pass_->commands_flat();
|
||||
}
|
||||
}
|
||||
|
||||
// Go ahead and throw down the shader command.
|
||||
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kShader);
|
||||
cmd_buffer_->PutInt(static_cast<int>(shading_type));
|
||||
}
|
||||
|
||||
void EnsureDrawing() {
|
||||
if (state_ != State::kDrawing) {
|
||||
WriteConfig();
|
||||
state_ = State::kDrawing;
|
||||
// make sure we're the only one drawing until we're submitted
|
||||
#if BA_DEBUG_BUILD
|
||||
assert(!pass_->frame_def()->defining_component());
|
||||
pass_->frame_def()->set_defining_component(true);
|
||||
#endif // BA_DEBUG_BUILD
|
||||
}
|
||||
}
|
||||
// subclasses should override this to dump
|
||||
// their needed data to the stream
|
||||
virtual void WriteConfig() = 0;
|
||||
|
||||
protected:
|
||||
RenderCommandBuffer* cmd_buffer_;
|
||||
State state_;
|
||||
RenderPass* pass_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_RENDER_COMPONENT_H_
|
||||
9
src/ballistica/graphics/component/shield_component.cc
Normal file
9
src/ballistica/graphics/component/shield_component.cc
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/shield_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void ShieldComponent::WriteConfig() { ConfigForShading(ShadingType::kShield); }
|
||||
|
||||
} // namespace ballistica
|
||||
21
src/ballistica/graphics/component/shield_component.h
Normal file
21
src/ballistica/graphics/component/shield_component.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_SHIELD_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_SHIELD_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// handles special cases such as drawing light/shadow/back buffers.
|
||||
class ShieldComponent : public RenderComponent {
|
||||
public:
|
||||
explicit ShieldComponent(RenderPass* pass) : RenderComponent(pass) {}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_SHIELD_COMPONENT_H_
|
||||
228
src/ballistica/graphics/component/simple_component.cc
Normal file
228
src/ballistica/graphics/component/simple_component.cc
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/simple_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void SimpleComponent::WriteConfig() {
|
||||
// if we're transparent we don't want to do optimization-based
|
||||
// shader swapping (ie: when color is 1). This is because it can
|
||||
// affect draw order, which is important unlike with opaque stuff.
|
||||
if (transparent_) {
|
||||
if (texture_.exists()) {
|
||||
if (colorize_texture_.exists()) {
|
||||
assert(flatness_ == 0.0f); // unimplemented combo
|
||||
assert(glow_amount_ == 0.0f); // unimplemented combo
|
||||
assert(shadow_opacity_ == 0.0f); // unimplemented combo
|
||||
assert(!double_sided_); // unimplemented combo
|
||||
assert(!mask_uv2_texture_.exists()); // unimplemented combo
|
||||
if (do_colorize_2_) {
|
||||
if (mask_texture_.exists()) {
|
||||
ConfigForShading(
|
||||
ShadingType::
|
||||
kSimpleTextureModulatedTransparentColorized2Masked);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
cmd_buffer_->PutTexture(mask_texture_);
|
||||
} else {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentColorized2);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
}
|
||||
} else {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentColorized);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
}
|
||||
} else {
|
||||
// non-colorized with texture
|
||||
if (double_sided_) {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(flatness_ == 0.0f); // unimplemented combo
|
||||
assert(glow_amount_ == 0.0f); // unimplemented combo
|
||||
assert(shadow_opacity_ == 0.0f); // unimplemented combo
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(!mask_uv2_texture_.exists()); // unimplemented combo
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentDoubleSided);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
if (shadow_opacity_ > 0.0f) {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(glow_amount_ == 0.0f); // unimplemented combo
|
||||
assert(mask_uv2_texture_.exists());
|
||||
if (flatness_ != 0.0f) {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTexModulatedTransShadowFlatness);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
shadow_offset_x_, shadow_offset_y_,
|
||||
shadow_blur_, shadow_opacity_, flatness_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(mask_uv2_texture_);
|
||||
} else {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentShadow);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
shadow_offset_x_, shadow_offset_y_,
|
||||
shadow_blur_, shadow_opacity_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(mask_uv2_texture_);
|
||||
}
|
||||
} else {
|
||||
if (glow_amount_ > 0.0f) {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(flatness_ == 0.0f); // unimplemented combo
|
||||
if (mask_uv2_texture_.exists()) {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentGlowMaskUV2);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
glow_amount_, glow_blur_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(mask_uv2_texture_);
|
||||
} else {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparentGlow);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
glow_amount_, glow_blur_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
}
|
||||
} else {
|
||||
if (flatness_ != 0.0f) {
|
||||
assert(!mask_texture_.exists()); // unimplemented
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransFlatness);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
flatness_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
if (mask_texture_.exists()) {
|
||||
// currently mask functionality requires colorize too, so
|
||||
// just send a black texture for that..
|
||||
ConfigForShading(
|
||||
ShadingType::
|
||||
kSimpleTextureModulatedTransparentColorized2Masked);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(
|
||||
color_r_, color_g_, color_b_, color_a_, colorize_color_r_,
|
||||
colorize_color_g_, colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(
|
||||
g_media->GetTexture(SystemTextureID::kBlack));
|
||||
cmd_buffer_->PutTexture(mask_texture_);
|
||||
} else {
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
color_a_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(flatness_ == 0.0f); // unimplemented combo
|
||||
assert(glow_amount_ == 0.0f); // unimplemented combo
|
||||
assert(shadow_opacity_ == 0.0f); // unimplemented combo
|
||||
assert(!colorize_texture_.exists()); // unimplemented combo
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(!mask_uv2_texture_.exists()); // unimplemented combo
|
||||
if (double_sided_) {
|
||||
ConfigForShading(ShadingType::kSimpleColorTransparentDoubleSided);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kSimpleColorTransparent);
|
||||
cmd_buffer_->PutInt(premultiplied_);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// when we're opaque we can do some shader-swapping optimizations
|
||||
// since draw order doesn't matter.
|
||||
assert(flatness_ == 0.0f); // unimplemented combo
|
||||
assert(glow_amount_ == 0.0f); // unimplemented combo
|
||||
assert(shadow_opacity_ == 0.0f); // unimplemented combo
|
||||
assert(!double_sided_); // not implemented
|
||||
assert(!mask_uv2_texture_.exists()); // unimplemented combo
|
||||
if (texture_.exists()) {
|
||||
if (colorize_texture_.exists()) {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
if (do_colorize_2_) {
|
||||
ConfigForShading(ShadingType::kSimpleTextureModulatedColorized2);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kSimpleTextureModulatedColorized);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(colorize_texture_);
|
||||
}
|
||||
} else {
|
||||
assert(!do_colorize_2_); // unsupported combo
|
||||
if (mask_texture_.exists()) {
|
||||
// currently mask functionality requires colorize too, so
|
||||
// we have to send a black texture along for that..
|
||||
ConfigForShading(
|
||||
ShadingType::kSimpleTextureModulatedColorized2Masked);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
|
||||
colorize_color_r_, colorize_color_g_,
|
||||
colorize_color_b_, colorize_color2_r_,
|
||||
colorize_color2_g_, colorize_color2_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
cmd_buffer_->PutTexture(g_media->GetTexture(SystemTextureID::kBlack));
|
||||
cmd_buffer_->PutTexture(mask_texture_);
|
||||
} else {
|
||||
// if no color was provided we can do a super-cheap version
|
||||
if (!have_color_) {
|
||||
ConfigForShading(ShadingType::kSimpleTexture);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kSimpleTextureModulated);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(!mask_texture_.exists()); // unimplemented combo
|
||||
assert(!colorize_texture_.exists()); // unsupported here
|
||||
ConfigForShading(ShadingType::kSimpleColor);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ballistica
|
||||
185
src/ballistica/graphics/component/simple_component.h
Normal file
185
src/ballistica/graphics/component/simple_component.h
Normal file
@ -0,0 +1,185 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_SIMPLE_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_SIMPLE_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// used for UI and overlays and things - no world tinting/etc is applied
|
||||
class SimpleComponent : public RenderComponent {
|
||||
public:
|
||||
explicit SimpleComponent(RenderPass* pass)
|
||||
: RenderComponent(pass),
|
||||
color_r_(1.0f),
|
||||
color_g_(1.0f),
|
||||
color_b_(1.0f),
|
||||
color_a_(1.0f),
|
||||
colorize_color_r_(1.0f),
|
||||
colorize_color_g_(1.0f),
|
||||
colorize_color_b_(1.0f),
|
||||
colorize_color_a_(1.0f),
|
||||
colorize_color2_r_(1.0f),
|
||||
colorize_color2_g_(1.0f),
|
||||
colorize_color2_b_(1.0f),
|
||||
colorize_color2_a_(1.0f),
|
||||
shadow_offset_x_(0.0f),
|
||||
shadow_offset_y_(0.0f),
|
||||
shadow_blur_(0.0f),
|
||||
shadow_opacity_(0.0f),
|
||||
glow_amount_(0.0f),
|
||||
glow_blur_(0.0f),
|
||||
flatness_(0.0f),
|
||||
transparent_(false),
|
||||
premultiplied_(false),
|
||||
have_color_(false),
|
||||
double_sided_(false),
|
||||
do_colorize_2_(false) {}
|
||||
void SetPremultiplied(bool val) {
|
||||
EnsureConfiguring();
|
||||
premultiplied_ = val;
|
||||
}
|
||||
void SetTransparent(bool val) {
|
||||
EnsureConfiguring();
|
||||
transparent_ = val;
|
||||
}
|
||||
void SetTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
texture_ = t;
|
||||
}
|
||||
void SetTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
texture_ = t_in->texture_data();
|
||||
} else {
|
||||
texture_.Clear();
|
||||
}
|
||||
}
|
||||
// used with colorize color 1 and 2
|
||||
// red areas of the texture will get multiplied by colorize-color1
|
||||
// and green areas by colorize-color2
|
||||
void SetColorizeTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
colorize_texture_ = t_in->texture_data();
|
||||
} else {
|
||||
colorize_texture_.Clear();
|
||||
}
|
||||
}
|
||||
void SetColorizeTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
colorize_texture_ = t;
|
||||
}
|
||||
// red multiplies source color, green adds colorize1-color,
|
||||
// and blue adds white
|
||||
// (currently requires colorize1 and colorize 2 to be set)
|
||||
void SetMaskTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
mask_texture_ = t_in->texture_data();
|
||||
} else {
|
||||
mask_texture_.Clear();
|
||||
}
|
||||
}
|
||||
void SetMaskTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
mask_texture_ = t;
|
||||
}
|
||||
void SetMaskUV2Texture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
mask_uv2_texture_ = t_in->texture_data();
|
||||
} else {
|
||||
mask_uv2_texture_.Clear();
|
||||
}
|
||||
}
|
||||
void SetMaskUV2Texture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
mask_uv2_texture_ = t;
|
||||
}
|
||||
void clearMaskUV2Texture() {
|
||||
EnsureConfiguring();
|
||||
mask_uv2_texture_.Clear();
|
||||
}
|
||||
void SetDoubleSided(bool enable) {
|
||||
EnsureConfiguring();
|
||||
double_sided_ = enable;
|
||||
}
|
||||
void SetColor(float r, float g, float b, float a = 1.0f) {
|
||||
// we support fast inline color changes with drawing streams
|
||||
// (avoids having to re-send a whole configure for every color change)
|
||||
// ..make sure to only allow this if we have a color already; otherwise we
|
||||
// need to config since we might be implicitly switch shaders by setting
|
||||
// color
|
||||
if (state_ == State::kDrawing && have_color_) {
|
||||
cmd_buffer_->PutCommand(
|
||||
RenderCommandBuffer::Command::kSimpleComponentInlineColor);
|
||||
cmd_buffer_->PutFloats(r, g, b, a);
|
||||
} else {
|
||||
EnsureConfiguring();
|
||||
have_color_ = true;
|
||||
}
|
||||
color_r_ = r;
|
||||
color_g_ = g;
|
||||
color_b_ = b;
|
||||
color_a_ = a;
|
||||
}
|
||||
void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
colorize_color_r_ = r;
|
||||
colorize_color_g_ = g;
|
||||
colorize_color_b_ = b;
|
||||
colorize_color_a_ = a;
|
||||
}
|
||||
void SetColorizeColor2(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
colorize_color2_r_ = r;
|
||||
colorize_color2_g_ = g;
|
||||
colorize_color2_b_ = b;
|
||||
colorize_color2_a_ = a;
|
||||
do_colorize_2_ = true;
|
||||
}
|
||||
void SetShadow(float offsetX, float offsetY, float blur, float opacity) {
|
||||
EnsureConfiguring();
|
||||
shadow_offset_x_ = offsetX;
|
||||
shadow_offset_y_ = offsetY;
|
||||
shadow_blur_ = blur;
|
||||
shadow_opacity_ = opacity;
|
||||
}
|
||||
void setGlow(float amount, float blur) {
|
||||
EnsureConfiguring();
|
||||
glow_amount_ = amount;
|
||||
glow_blur_ = blur;
|
||||
}
|
||||
void SetFlatness(float flatness) {
|
||||
EnsureConfiguring();
|
||||
flatness_ = flatness;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
|
||||
protected:
|
||||
float color_r_, color_g_, color_b_, color_a_;
|
||||
float colorize_color_r_, colorize_color_g_, colorize_color_b_,
|
||||
colorize_color_a_;
|
||||
float colorize_color2_r_, colorize_color2_g_, colorize_color2_b_,
|
||||
colorize_color2_a_;
|
||||
float shadow_offset_x_, shadow_offset_y_, shadow_blur_, shadow_opacity_;
|
||||
float glow_amount_, glow_blur_;
|
||||
float flatness_;
|
||||
Object::Ref<TextureData> texture_;
|
||||
Object::Ref<TextureData> colorize_texture_;
|
||||
Object::Ref<TextureData> mask_texture_;
|
||||
Object::Ref<TextureData> mask_uv2_texture_;
|
||||
bool do_colorize_2_;
|
||||
bool transparent_;
|
||||
bool premultiplied_;
|
||||
bool have_color_;
|
||||
bool double_sided_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_SIMPLE_COMPONENT_H_
|
||||
19
src/ballistica/graphics/component/smoke_component.cc
Normal file
19
src/ballistica/graphics/component/smoke_component.cc
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/smoke_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void SmokeComponent::WriteConfig() {
|
||||
if (overlay_) {
|
||||
ConfigForShading(ShadingType::kSmokeOverlay);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutTexture(g_media->GetTexture(SystemTextureID::kSmoke));
|
||||
} else {
|
||||
ConfigForShading(ShadingType::kSmoke);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutTexture(g_media->GetTexture(SystemTextureID::kSmoke));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
39
src/ballistica/graphics/component/smoke_component.h
Normal file
39
src/ballistica/graphics/component/smoke_component.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_SMOKE_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_SMOKE_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class SmokeComponent : public RenderComponent {
|
||||
public:
|
||||
explicit SmokeComponent(RenderPass* pass)
|
||||
: RenderComponent(pass),
|
||||
color_r_(1.0f),
|
||||
color_g_(1.0f),
|
||||
color_b_(1.0f),
|
||||
color_a_(1.0f),
|
||||
overlay_(false) {}
|
||||
void SetColor(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
color_r_ = r;
|
||||
color_g_ = g;
|
||||
color_b_ = b;
|
||||
color_a_ = a;
|
||||
}
|
||||
void SetOverlay(bool overlay) {
|
||||
EnsureConfiguring();
|
||||
overlay_ = overlay;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
float color_r_, color_g_, color_b_, color_a_;
|
||||
bool overlay_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_SMOKE_COMPONENT_H_
|
||||
12
src/ballistica/graphics/component/special_component.cc
Normal file
12
src/ballistica/graphics/component/special_component.cc
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/special_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void SpecialComponent::WriteConfig() {
|
||||
ConfigForShading(ShadingType::kSpecial);
|
||||
cmd_buffer_->PutInt(static_cast<int>(source_));
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
26
src/ballistica/graphics/component/special_component.h
Normal file
26
src/ballistica/graphics/component/special_component.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_SPECIAL_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_SPECIAL_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// handles special cases such as drawing light/shadow/back buffers.
|
||||
class SpecialComponent : public RenderComponent {
|
||||
public:
|
||||
enum class Source { kLightBuffer, kLightShadowBuffer, kVROverlayBuffer };
|
||||
SpecialComponent(RenderPass* pass, Source s)
|
||||
: RenderComponent(pass), source_(s) {}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
|
||||
private:
|
||||
Source source_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_SPECIAL_COMPONENT_H_
|
||||
25
src/ballistica/graphics/component/sprite_component.cc
Normal file
25
src/ballistica/graphics/component/sprite_component.cc
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/component/sprite_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void SpriteComponent::WriteConfig() {
|
||||
// if they didn't give us a texture, just use a blank white texture;
|
||||
// this is not a common case and easier than forking all our shaders
|
||||
// to create non-textured versions.
|
||||
if (!texture_.exists()) {
|
||||
texture_ = g_media->GetTexture(SystemTextureID::kWhite);
|
||||
}
|
||||
if (exponent_ == 1) {
|
||||
ConfigForShading(ShadingType::kSprite);
|
||||
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_);
|
||||
cmd_buffer_->PutInt(overlay_);
|
||||
cmd_buffer_->PutInt(camera_aligned_);
|
||||
cmd_buffer_->PutTexture(texture_);
|
||||
} else {
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
61
src/ballistica/graphics/component/sprite_component.h
Normal file
61
src/ballistica/graphics/component/sprite_component.h
Normal file
@ -0,0 +1,61 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_COMPONENT_SPRITE_COMPONENT_H_
|
||||
#define BALLISTICA_GRAPHICS_COMPONENT_SPRITE_COMPONENT_H_
|
||||
|
||||
#include "ballistica/graphics/component/render_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class SpriteComponent : public RenderComponent {
|
||||
public:
|
||||
explicit SpriteComponent(RenderPass* pass) : RenderComponent(pass) {}
|
||||
void SetColor(float r, float g, float b, float a = 1.0f) {
|
||||
EnsureConfiguring();
|
||||
color_r_ = r;
|
||||
color_g_ = g;
|
||||
color_b_ = b;
|
||||
color_a_ = a;
|
||||
have_color_ = true;
|
||||
}
|
||||
void SetCameraAligned(bool c) {
|
||||
EnsureConfiguring();
|
||||
camera_aligned_ = c;
|
||||
}
|
||||
void SetOverlay(bool enable) {
|
||||
EnsureConfiguring();
|
||||
overlay_ = enable;
|
||||
}
|
||||
void SetExponent(int i) {
|
||||
EnsureConfiguring();
|
||||
exponent_ = static_cast_check_fit<uint8_t>(i);
|
||||
}
|
||||
void SetTexture(const Object::Ref<Texture>& t_in) {
|
||||
EnsureConfiguring();
|
||||
if (t_in.exists()) {
|
||||
texture_ = t_in->texture_data();
|
||||
} else {
|
||||
texture_.Clear();
|
||||
}
|
||||
}
|
||||
void SetTexture(TextureData* t) {
|
||||
EnsureConfiguring();
|
||||
texture_ = t;
|
||||
}
|
||||
|
||||
protected:
|
||||
void WriteConfig() override;
|
||||
bool have_color_{};
|
||||
bool camera_aligned_{};
|
||||
bool overlay_{};
|
||||
uint8_t exponent_{1};
|
||||
float color_r_{1.0f};
|
||||
float color_g_{1.0f};
|
||||
float color_b_{1.0f};
|
||||
float color_a_{1.0f};
|
||||
Object::Ref<TextureData> texture_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_COMPONENT_SPRITE_COMPONENT_H_
|
||||
173
src/ballistica/graphics/frame_def.cc
Normal file
173
src/ballistica/graphics/frame_def.cc
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/frame_def.h"
|
||||
|
||||
#include "ballistica/graphics/camera.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/render_pass.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
FrameDef::FrameDef()
|
||||
: light_pass_(new RenderPass(RenderPass::Type::kLightPass, this)),
|
||||
light_shadow_pass_(
|
||||
new RenderPass(RenderPass::Type::kLightShadowPass, this)),
|
||||
beauty_pass_(new RenderPass(RenderPass::Type::kBeautyPass, this)),
|
||||
beauty_pass_bg_(new RenderPass(RenderPass::Type::kBeautyPassBG, this)),
|
||||
overlay_pass_(new RenderPass(RenderPass::Type::kOverlayPass, this)),
|
||||
overlay_front_pass_(
|
||||
new RenderPass(RenderPass::Type::kOverlayFrontPass, this)),
|
||||
overlay_3d_pass_(new RenderPass(RenderPass::Type::kOverlay3DPass, this)),
|
||||
vr_cover_pass_(new RenderPass(RenderPass::Type::kVRCoverPass, this)),
|
||||
overlay_fixed_pass_(
|
||||
new RenderPass(RenderPass::Type::kOverlayFixedPass, this)),
|
||||
overlay_flat_pass_(
|
||||
new RenderPass(RenderPass::Type::kOverlayFlatPass, this)),
|
||||
blit_pass_(new RenderPass(RenderPass::Type::kBlitPass, this)) {}
|
||||
|
||||
FrameDef::~FrameDef() { assert(InGameThread()); }
|
||||
|
||||
void FrameDef::Reset() {
|
||||
assert(InGameThread());
|
||||
real_time_ = 0;
|
||||
base_time_ = 0;
|
||||
base_time_elapsed_ = 0;
|
||||
frame_number_ = 0;
|
||||
|
||||
#if BA_DEBUG_BUILD
|
||||
defining_component_ = false;
|
||||
#endif
|
||||
|
||||
benchmark_type_ = BenchmarkType::kNone;
|
||||
|
||||
mesh_data_creates_.clear();
|
||||
mesh_data_destroys_.clear();
|
||||
|
||||
media_components_.clear();
|
||||
meshes_.clear();
|
||||
mesh_index_sizes_.clear();
|
||||
mesh_buffers_.clear();
|
||||
|
||||
quality_ = g_graphics_server->quality();
|
||||
|
||||
assert(g_graphics->has_supports_high_quality_graphics_value());
|
||||
orbiting_ = (g_graphics->camera()->mode() == CameraMode::kOrbit);
|
||||
|
||||
shadow_offset_ = g_graphics->shadow_offset();
|
||||
shadow_scale_ = g_graphics->shadow_scale();
|
||||
shadow_ortho_ = g_graphics->shadow_ortho();
|
||||
tint_ = g_graphics->tint();
|
||||
ambient_color_ = g_graphics->ambient_color();
|
||||
|
||||
vignette_outer_ = g_graphics->vignette_outer();
|
||||
vignette_inner_ = g_graphics->vignette_inner();
|
||||
|
||||
light_pass_->Reset();
|
||||
light_shadow_pass_->Reset();
|
||||
beauty_pass_->Reset();
|
||||
beauty_pass_bg_->Reset();
|
||||
overlay_pass_->Reset();
|
||||
overlay_front_pass_->Reset();
|
||||
if (IsVRMode()) {
|
||||
overlay_flat_pass_->Reset();
|
||||
overlay_fixed_pass_->Reset();
|
||||
vr_cover_pass_->Reset();
|
||||
}
|
||||
overlay_3d_pass_->Reset();
|
||||
blit_pass_->Reset();
|
||||
beauty_pass_->set_floor_reflection(g_graphics->floor_reflection());
|
||||
}
|
||||
|
||||
void FrameDef::Finalize() {
|
||||
assert(!defining_component_);
|
||||
light_pass_->Finalize();
|
||||
light_shadow_pass_->Finalize();
|
||||
beauty_pass_->Finalize();
|
||||
beauty_pass_bg_->Finalize();
|
||||
overlay_pass_->Finalize();
|
||||
overlay_front_pass_->Finalize();
|
||||
if (IsVRMode()) {
|
||||
overlay_fixed_pass_->Finalize();
|
||||
overlay_flat_pass_->Finalize();
|
||||
vr_cover_pass_->Finalize();
|
||||
}
|
||||
overlay_3d_pass_->Finalize();
|
||||
blit_pass_->Finalize();
|
||||
}
|
||||
|
||||
void FrameDef::AddMesh(Mesh* mesh) {
|
||||
// Add this mesh's data to the frame only if we haven't yet.
|
||||
if (mesh->last_frame_def_num() != frame_number_) {
|
||||
mesh->set_last_frame_def_num(frame_number_);
|
||||
meshes_.push_back(mesh->mesh_data_client_handle());
|
||||
switch (mesh->type()) {
|
||||
case MeshDataType::kIndexedSimpleSplit: {
|
||||
auto* m = static_cast<MeshIndexedSimpleSplit*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<MeshIndexedSimpleSplit*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->static_data());
|
||||
mesh_buffers_.emplace_back(m->dynamic_data());
|
||||
break;
|
||||
}
|
||||
case MeshDataType::kIndexedObjectSplit: {
|
||||
auto* m = static_cast<MeshIndexedObjectSplit*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<MeshIndexedObjectSplit*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->static_data());
|
||||
mesh_buffers_.emplace_back(m->dynamic_data());
|
||||
break;
|
||||
}
|
||||
case MeshDataType::kIndexedSimpleFull: {
|
||||
auto* m = static_cast<MeshIndexedSimpleFull*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<MeshIndexedSimpleFull*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->data());
|
||||
break;
|
||||
}
|
||||
case MeshDataType::kIndexedDualTextureFull: {
|
||||
auto* m = static_cast<MeshIndexedDualTextureFull*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<MeshIndexedDualTextureFull*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->data());
|
||||
break;
|
||||
}
|
||||
case MeshDataType::kIndexedSmokeFull: {
|
||||
auto* m = static_cast<MeshIndexedSmokeFull*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<MeshIndexedSmokeFull*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->data());
|
||||
break;
|
||||
}
|
||||
case MeshDataType::kSprite: {
|
||||
auto* m = static_cast<SpriteMesh*>(mesh);
|
||||
assert(m);
|
||||
assert(m == dynamic_cast<SpriteMesh*>(mesh));
|
||||
mesh_index_sizes_.push_back(
|
||||
static_cast_check_fit<int8_t>(m->index_data_size()));
|
||||
mesh_buffers_.emplace_back(m->GetIndexData());
|
||||
mesh_buffers_.emplace_back(m->data());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
228
src/ballistica/graphics/frame_def.h
Normal file
228
src/ballistica/graphics/frame_def.h
Normal file
@ -0,0 +1,228 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_FRAME_DEF_H_
|
||||
#define BALLISTICA_GRAPHICS_FRAME_DEF_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/math/matrix44f.h"
|
||||
#include "ballistica/math/vector2f.h"
|
||||
#include "ballistica/media/data/media_component_data.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// A flattened representation of a frame; generated by the game thread and sent
|
||||
/// to the graphics thread to render.
|
||||
class FrameDef {
|
||||
public:
|
||||
auto light_pass() -> RenderPass* { return light_pass_.get(); }
|
||||
auto light_shadow_pass() -> RenderPass* { return light_shadow_pass_.get(); }
|
||||
auto beauty_pass() -> RenderPass* { return beauty_pass_.get(); }
|
||||
auto beauty_pass_bg() -> RenderPass* { return beauty_pass_bg_.get(); }
|
||||
auto overlay_pass() -> RenderPass* { return overlay_pass_.get(); }
|
||||
auto overlay_front_pass() -> RenderPass* { return overlay_front_pass_.get(); }
|
||||
auto vr_near_clip() const -> float { return vr_near_clip_; }
|
||||
void set_vr_near_clip(float val) { vr_near_clip_ = val; }
|
||||
auto benchmark_type() const -> BenchmarkType { return benchmark_type_; }
|
||||
void set_benchmark_type(BenchmarkType val) { benchmark_type_ = val; }
|
||||
|
||||
// Returns the fixed overlay pass if there is one; otherwise the regular.
|
||||
auto GetOverlayFixedPass() -> RenderPass* {
|
||||
if (IsVRMode()) {
|
||||
return overlay_fixed_pass_.get();
|
||||
} else {
|
||||
return overlay_pass_.get();
|
||||
}
|
||||
}
|
||||
|
||||
// Return either the overlay-flat pass (in vr) or regular overlay pass (for
|
||||
// non-vr).
|
||||
auto GetOverlayFlatPass() -> RenderPass* {
|
||||
if (IsVRMode()) {
|
||||
return overlay_flat_pass_.get();
|
||||
} else {
|
||||
return overlay_pass_.get();
|
||||
}
|
||||
}
|
||||
auto overlay_3d_pass() -> RenderPass* { return overlay_3d_pass_.get(); }
|
||||
auto blit_pass() -> RenderPass* { return blit_pass_.get(); }
|
||||
auto vr_cover_pass() -> RenderPass* { return vr_cover_pass_.get(); }
|
||||
|
||||
// Returns the real-time this frame_def originated at.
|
||||
// For a more smoothly-incrementing value,
|
||||
// use getbasetime()
|
||||
auto real_time() const -> millisecs_t { return real_time_; }
|
||||
auto frame_number() const -> int64_t { return frame_number_; }
|
||||
|
||||
// Returns the bsGame master-net-time when this was made
|
||||
// (tries to match real time but is incremented more smoothly
|
||||
// so is better for drawing purposes)
|
||||
auto base_time() const -> millisecs_t { return base_time_; }
|
||||
|
||||
// How much base time does this frame-def represent.
|
||||
auto base_time_elapsed() const -> millisecs_t { return base_time_elapsed_; }
|
||||
|
||||
auto quality() const -> GraphicsQuality { return quality_; }
|
||||
auto orbiting() const -> bool { return orbiting_; }
|
||||
auto shadow_offset() const -> const Vector3f& { return shadow_offset_; }
|
||||
auto shadow_scale() const -> const Vector2f& { return shadow_scale_; }
|
||||
auto shadow_ortho() const -> bool { return shadow_ortho_; }
|
||||
auto tint() const -> const Vector3f& { return tint_; }
|
||||
auto ambient_color() const -> const Vector3f& { return ambient_color_; }
|
||||
auto vignette_outer() const -> const Vector3f& { return vignette_outer_; }
|
||||
auto vignette_inner() const -> const Vector3f& { return vignette_inner_; }
|
||||
|
||||
// FIXME: what was this for?..(I think some vr thing?)
|
||||
auto cam_original() const -> const Vector3f& { return cam_original_; }
|
||||
auto cam_target_original() const -> const Vector3f& {
|
||||
return cam_target_original_;
|
||||
}
|
||||
void set_cam_original(const Vector3f& val) { cam_original_ = val; }
|
||||
void set_cam_target_original(const Vector3f& val) {
|
||||
cam_target_original_ = val;
|
||||
}
|
||||
auto camera_mode() const -> CameraMode { return camera_mode_; }
|
||||
auto vr_overlay_screen_matrix() const -> const Matrix44f& {
|
||||
return vr_overlay_screen_matrix_;
|
||||
}
|
||||
void set_vr_overlay_screen_matrix(const Matrix44f& mat) {
|
||||
vr_overlay_screen_matrix_ = mat;
|
||||
}
|
||||
auto vr_overlay_screen_matrix_fixed() const -> const Matrix44f& {
|
||||
return vr_overlay_screen_matrix_fixed_;
|
||||
}
|
||||
void set_vr_overlay_screen_matrix_fixed(const Matrix44f& mat) {
|
||||
vr_overlay_screen_matrix_fixed_ = mat;
|
||||
}
|
||||
|
||||
// Effects requiring availability of a depth texture should
|
||||
// check this to determine whether they should draw.
|
||||
auto has_depth_texture() const -> bool {
|
||||
return (quality_ >= GraphicsQuality::kHigh);
|
||||
}
|
||||
void AddComponent(const Object::Ref<MediaComponentData>& component) {
|
||||
// Add a reference to this component only if we havn't yet.
|
||||
if (component->last_frame_def_num() != frame_number_) {
|
||||
component->set_last_frame_def_num(frame_number_);
|
||||
media_components_.push_back(component);
|
||||
}
|
||||
}
|
||||
void AddMesh(Mesh* mesh);
|
||||
void set_needs_clear(bool val) { needs_clear_ = val; }
|
||||
auto needs_clear() const -> bool { return needs_clear_; }
|
||||
|
||||
FrameDef();
|
||||
~FrameDef();
|
||||
void Reset();
|
||||
void Finalize();
|
||||
|
||||
void set_base_time_elapsed(millisecs_t val) { base_time_elapsed_ = val; }
|
||||
void set_real_time(millisecs_t val) { real_time_ = val; }
|
||||
void set_base_time(millisecs_t val) { base_time_ = val; }
|
||||
void set_frame_number(int64_t val) { frame_number_ = val; }
|
||||
|
||||
auto overlay_flat_pass() const -> RenderPass* {
|
||||
return overlay_flat_pass_.get();
|
||||
}
|
||||
auto overlay_fixed_pass() const -> RenderPass* {
|
||||
return overlay_fixed_pass_.get();
|
||||
}
|
||||
auto overlay_front_pass() const -> RenderPass* {
|
||||
return overlay_front_pass_.get();
|
||||
}
|
||||
auto overlay_pass() const -> RenderPass* { return overlay_pass_.get(); }
|
||||
auto vr_cover_pass() const -> RenderPass* { return vr_cover_pass_.get(); }
|
||||
|
||||
void set_mesh_data_creates(const std::vector<MeshData*>& creates) {
|
||||
mesh_data_creates_ = creates;
|
||||
}
|
||||
void set_mesh_data_destroys(const std::vector<MeshData*>& destroys) {
|
||||
mesh_data_destroys_ = destroys;
|
||||
}
|
||||
auto mesh_data_creates() const -> const std::vector<MeshData*>& {
|
||||
return mesh_data_creates_;
|
||||
}
|
||||
auto mesh_data_destroys() const -> const std::vector<MeshData*>& {
|
||||
return mesh_data_destroys_;
|
||||
}
|
||||
auto meshes() const -> const std::vector<Object::Ref<MeshDataClientHandle>>& {
|
||||
return meshes_;
|
||||
}
|
||||
auto mesh_buffers() const -> const std::vector<Object::Ref<MeshBufferBase>>& {
|
||||
return mesh_buffers_;
|
||||
}
|
||||
auto mesh_index_sizes() const -> const std::vector<int8_t>& {
|
||||
return mesh_index_sizes_;
|
||||
}
|
||||
auto media_components() const
|
||||
-> const std::vector<Object::Ref<MediaComponentData>>& {
|
||||
return media_components_;
|
||||
}
|
||||
|
||||
void set_camera_mode(CameraMode val) { camera_mode_ = val; }
|
||||
void set_rendering(bool val) { rendering_ = val; }
|
||||
auto rendering() const -> bool { return rendering_; }
|
||||
void set_shake_original(const Vector3f& val) { shake_original_ = val; }
|
||||
auto shake_original() const -> const Vector3f& { return shake_original_; }
|
||||
|
||||
#if BA_DEBUG_BUILD
|
||||
auto defining_component() const -> bool { return defining_component_; }
|
||||
void set_defining_component(bool val) { defining_component_ = val; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool needs_clear_{};
|
||||
BenchmarkType benchmark_type_{BenchmarkType::kNone};
|
||||
bool rendering_{};
|
||||
CameraMode camera_mode_{CameraMode::kFollow};
|
||||
Vector3f cam_original_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f cam_target_original_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f shake_original_{0.0f, 0.0f, 0.0f};
|
||||
float vr_near_clip_{};
|
||||
Matrix44f vr_overlay_screen_matrix_ = kMatrix44fIdentity;
|
||||
Matrix44f vr_overlay_screen_matrix_fixed_ = kMatrix44fIdentity;
|
||||
std::vector<MeshData*> mesh_data_creates_;
|
||||
std::vector<MeshData*> mesh_data_destroys_;
|
||||
|
||||
// Meshes/Buffers:
|
||||
std::vector<Object::Ref<MeshDataClientHandle>> meshes_;
|
||||
std::vector<Object::Ref<MeshBufferBase>> mesh_buffers_;
|
||||
std::vector<int8_t> mesh_index_sizes_;
|
||||
std::vector<Object::Ref<MediaComponentData>> media_components_;
|
||||
|
||||
#if BA_DEBUG_BUILD
|
||||
// Sanity checking: make sure components are completely submitted
|
||||
// before new ones are started (so we dont get scrambled command buffers).
|
||||
bool defining_component_{};
|
||||
#endif
|
||||
|
||||
std::unique_ptr<RenderPass> light_pass_;
|
||||
std::unique_ptr<RenderPass> light_shadow_pass_;
|
||||
std::unique_ptr<RenderPass> beauty_pass_;
|
||||
std::unique_ptr<RenderPass> beauty_pass_bg_;
|
||||
std::unique_ptr<RenderPass> overlay_pass_;
|
||||
std::unique_ptr<RenderPass> overlay_front_pass_;
|
||||
std::unique_ptr<RenderPass> overlay_fixed_pass_;
|
||||
std::unique_ptr<RenderPass> overlay_flat_pass_;
|
||||
std::unique_ptr<RenderPass> vr_cover_pass_;
|
||||
std::unique_ptr<RenderPass> overlay_3d_pass_;
|
||||
std::unique_ptr<RenderPass> blit_pass_;
|
||||
GraphicsQuality quality_{GraphicsQuality::kLow};
|
||||
bool orbiting_{};
|
||||
millisecs_t real_time_{};
|
||||
millisecs_t base_time_{};
|
||||
millisecs_t base_time_elapsed_{};
|
||||
int64_t frame_number_{};
|
||||
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector2f shadow_scale_{1.0f, 1.0f};
|
||||
bool shadow_ortho_{};
|
||||
Vector3f tint_{1.0f, 1.0f, 1.0f};
|
||||
Vector3f ambient_color_{1.0f, 1.0f, 1.0f};
|
||||
Vector3f vignette_outer_{1.0f, 1.0f, 1.0f};
|
||||
Vector3f vignette_inner_{1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_FRAME_DEF_H_
|
||||
19
src/ballistica/graphics/framebuffer.h
Normal file
19
src/ballistica/graphics/framebuffer.h
Normal file
@ -0,0 +1,19 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_FRAMEBUFFER_H_
|
||||
#define BALLISTICA_GRAPHICS_FRAMEBUFFER_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class Framebuffer : public Object {
|
||||
public:
|
||||
auto GetDefaultOwnerThread() const -> ThreadIdentifier override {
|
||||
return ThreadIdentifier::kMain;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_FRAMEBUFFER_H_
|
||||
368
src/ballistica/graphics/gl/gl_sys.cc
Normal file
368
src/ballistica/graphics/gl/gl_sys.cc
Normal file
@ -0,0 +1,368 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#if BA_ENABLE_OPENGL
|
||||
#include "ballistica/graphics/gl/gl_sys.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/platform/sdl/sdl_app.h"
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
#include <EGL/egl.h>
|
||||
#if !BA_USE_ES3_INCLUDES
|
||||
#include "ballistica/platform/android/android_gl3.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BA_OSTYPE_WINDOWS
|
||||
#pragma comment(lib, "opengl32.lib")
|
||||
#pragma comment(lib, "glu32.lib")
|
||||
#endif
|
||||
|
||||
#if BA_OSTYPE_MACOS
|
||||
#include <OpenGL/CGLContext.h>
|
||||
#include <OpenGL/CGLTypes.h>
|
||||
#include <OpenGL/OpenGL.h>
|
||||
#endif
|
||||
|
||||
#if BA_DEBUG_BUILD
|
||||
#define DEBUG_CHECK_GL_ERROR \
|
||||
{ \
|
||||
GLenum err = glGetError(); \
|
||||
if (err != GL_NO_ERROR) \
|
||||
Log("OPENGL ERROR AT LINE " + std::to_string(__LINE__) + ": " \
|
||||
+ GLErrorToString(err)); \
|
||||
}
|
||||
#else
|
||||
#define DEBUG_CHECK_GL_ERROR
|
||||
#endif
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT = nullptr;
|
||||
#endif
|
||||
|
||||
#if BA_OSTYPE_WINDOWS
|
||||
PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ = nullptr;
|
||||
PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
|
||||
glGetFramebufferAttachmentParameteriv = nullptr;
|
||||
PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate = nullptr;
|
||||
PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr;
|
||||
PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = nullptr;
|
||||
PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB = nullptr;
|
||||
PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB = nullptr;
|
||||
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
|
||||
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
|
||||
PFNGLCREATESHADERPROC glCreateShader = nullptr;
|
||||
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
|
||||
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
|
||||
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
|
||||
PFNGLGETINFOLOGARBPROC glGetInfoLogARB = nullptr;
|
||||
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
|
||||
PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram = nullptr;
|
||||
PFNGLGENERATEMIPMAPPROC glGenerateMipmap = nullptr;
|
||||
PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = nullptr;
|
||||
PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = nullptr;
|
||||
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
|
||||
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
|
||||
PFNGLUNIFORM1IPROC glUniform1i = nullptr;
|
||||
PFNGLUNIFORM1FPROC glUniform1f = nullptr;
|
||||
PFNGLUNIFORM1FVPROC glUniform1fv = nullptr;
|
||||
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
|
||||
PFNGLUNIFORM3FPROC glUniform3f = nullptr;
|
||||
PFNGLUNIFORM4FPROC glUniform4f = nullptr;
|
||||
PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = nullptr;
|
||||
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
|
||||
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
|
||||
PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = nullptr;
|
||||
PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = nullptr;
|
||||
PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = nullptr;
|
||||
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
|
||||
PFNGLBUFFERDATAPROC glBufferData = nullptr;
|
||||
PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = nullptr;
|
||||
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample =
|
||||
nullptr;
|
||||
PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = nullptr;
|
||||
PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = nullptr;
|
||||
PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = nullptr;
|
||||
PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = nullptr;
|
||||
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
|
||||
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
|
||||
PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = nullptr;
|
||||
PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv = nullptr;
|
||||
PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation = nullptr;
|
||||
PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr;
|
||||
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
|
||||
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
|
||||
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
|
||||
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
|
||||
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
|
||||
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
|
||||
PFNGLDETACHSHADERPROC glDetachShader = nullptr;
|
||||
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
|
||||
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
|
||||
#endif // BA_OSTYPE_WINDOWS
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
|
||||
#pragma ide diagnostic ignored "EmptyDeclOrStmt"
|
||||
|
||||
GLContext::GLContext(int target_res_x, int target_res_y, bool fullscreen)
|
||||
: fullscreen_(fullscreen) {
|
||||
assert(InMainThread());
|
||||
bool need_window = true;
|
||||
#if BA_RIFT_BUILD
|
||||
// on the rift build we don't need a window when running in vr mode; we just
|
||||
// use the context we're created into...
|
||||
if (IsVRMode()) {
|
||||
need_window = false;
|
||||
}
|
||||
#endif // BA_RIFT_BUILD
|
||||
if (explicit_bool(need_window)) {
|
||||
#if BA_SDL2_BUILD
|
||||
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS;
|
||||
#else
|
||||
// Things are a bit more varied on desktop..
|
||||
uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
|
||||
| SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
|
||||
if (fullscreen_) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
#endif
|
||||
sdl_window_ = SDL_CreateWindow(nullptr, SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED, target_res_x,
|
||||
target_res_y, flags);
|
||||
if (!sdl_window_) {
|
||||
throw Exception("Unable to create SDL Window of size "
|
||||
+ std::to_string(target_res_x) + " by "
|
||||
+ std::to_string(target_res_y));
|
||||
}
|
||||
sdl_gl_context_ = SDL_GL_CreateContext(sdl_window_);
|
||||
if (!sdl_gl_context_) {
|
||||
throw Exception("Unable to create SDL GL Context");
|
||||
}
|
||||
SDL_SetWindowTitle(sdl_window_, "BallisticaCore");
|
||||
|
||||
// Our actual drawable size could differ from the window size on retina
|
||||
// devices.
|
||||
int win_size_x, win_size_y;
|
||||
SDL_GetWindowSize(sdl_window_, &win_size_x, &win_size_y);
|
||||
SDLApp::get()->SetInitialScreenDimensions(Vector2f(
|
||||
static_cast<float>(win_size_x), static_cast<float>(win_size_y)));
|
||||
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
res_x_ = win_size_x;
|
||||
res_y_ = win_size_y;
|
||||
#else
|
||||
SDL_GL_GetDrawableSize(sdl_window_, &res_x_, &res_y_);
|
||||
#endif // BA_OSTYPE_ANDROID
|
||||
|
||||
// This can come through as zero in some cases (on our cardboard build at
|
||||
// least).
|
||||
if (win_size_x != 0) {
|
||||
pixel_density_ =
|
||||
static_cast<float>(res_x_) / static_cast<float>(win_size_x);
|
||||
}
|
||||
#elif BA_SDL_BUILD // BA_SDL2_BUILD
|
||||
|
||||
int v_flags;
|
||||
v_flags = SDL_OPENGL;
|
||||
if (fullscreen_) {
|
||||
v_flags |= SDL_FULLSCREEN;
|
||||
// convert to the closest valid fullscreen resolution
|
||||
// (our last 1.2 build is mac and it's got hacked-in fullscreen-window
|
||||
// support; so we don't need this) getValidResolution(target_res_x,
|
||||
// target_res_y);
|
||||
} else {
|
||||
v_flags |= SDL_RESIZABLE;
|
||||
}
|
||||
surface_ = SDL_SetVideoMode(target_res_x, target_res_y, 32, v_flags);
|
||||
|
||||
// if we failed, fall back to windowed mode.
|
||||
if (surface_ == nullptr) {
|
||||
throw Exception("SDL_SetVideoMode() failed for "
|
||||
+ std::to_string(target_res_x) + " by "
|
||||
+ std::to_string(target_res_y) + " fullscreen="
|
||||
+ std::to_string(static_cast<int>(fullscreen_)));
|
||||
}
|
||||
res_x_ = surface_->w;
|
||||
res_y_ = surface_->h;
|
||||
SDLApp::get()->SetInitialScreenDimensions(Vector2f(res_x_, res_y_));
|
||||
SDL_WM_SetCaption("BallisticaCore", "BallisticaCore");
|
||||
#elif BA_OSTYPE_ANDROID
|
||||
// On Android the Java layer creates a GL setup before even calling us.
|
||||
// So we have nothing to do here. Hooray!
|
||||
#else
|
||||
throw Exception("FIXME: Unimplemented");
|
||||
#endif // BA_SDL2_BUILD
|
||||
}
|
||||
|
||||
// Fetch needed android gl stuff.
|
||||
#if BA_OSTYPE_ANDROID
|
||||
#define GET(PTRTYPE, FUNC, REQUIRED) \
|
||||
FUNC = (PTRTYPE)eglGetProcAddress(#FUNC); \
|
||||
if (!FUNC) FUNC = (PTRTYPE)eglGetProcAddress(#FUNC "EXT"); \
|
||||
if (REQUIRED) { \
|
||||
BA_PRECONDITION(FUNC != nullptr); \
|
||||
}
|
||||
GET(PFNGLDISCARDFRAMEBUFFEREXTPROC, _glDiscardFramebufferEXT, false);
|
||||
#endif // BA_OSTYPE_ANDROID
|
||||
|
||||
// Fetch needed windows gl stuff.
|
||||
#if BA_OSTYPE_WINDOWS
|
||||
#define GET(PTRTYPE, FUNC, REQUIRED) \
|
||||
FUNC = (PTRTYPE)wglGetProcAddress(#FUNC); \
|
||||
if (!FUNC) FUNC = (PTRTYPE)wglGetProcAddress(#FUNC "EXT"); \
|
||||
if (REQUIRED) { \
|
||||
BA_PRECONDITION(FUNC != nullptr); \
|
||||
}
|
||||
GET(PFNGLGETINTERNALFORMATIVPROC, glGetInternalformativ,
|
||||
false); // for checking msaa level support
|
||||
GET(PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC,
|
||||
glGetFramebufferAttachmentParameteriv, false); // for checking srgb stuff
|
||||
GET(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate,
|
||||
false); // needed for VR overlay
|
||||
GET(PFNGLACTIVETEXTUREPROC, glActiveTexture, true);
|
||||
GET(PFNGLCLIENTACTIVETEXTUREARBPROC, glClientActiveTextureARB, true);
|
||||
GET(PFNWGLSWAPINTERVALEXTPROC, wglSwapIntervalEXT, true);
|
||||
GET(PFNGLPOINTPARAMETERFVARBPROC, glPointParameterfvARB, true);
|
||||
GET(PFNGLPOINTPARAMETERFARBPROC, glPointParameterfARB, true);
|
||||
GET(PFNGLCREATEPROGRAMPROC, glCreateProgram, true);
|
||||
GET(PFNGLCREATESHADERPROC, glCreateShader, true);
|
||||
GET(PFNGLSHADERSOURCEPROC, glShaderSource, true);
|
||||
GET(PFNGLCOMPILESHADERPROC, glCompileShader, true);
|
||||
GET(PFNGLLINKPROGRAMPROC, glLinkProgram, true);
|
||||
GET(PFNGLGETINFOLOGARBPROC, glGetInfoLogARB, true);
|
||||
GET(PFNGLATTACHSHADERPROC, glAttachShader, true);
|
||||
GET(PFNGLUSEPROGRAMOBJECTARBPROC, glUseProgram, true);
|
||||
GET(PFNGLGENERATEMIPMAPPROC, glGenerateMipmap, true);
|
||||
GET(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer, true);
|
||||
GET(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation, true);
|
||||
GET(PFNGLUNIFORM1IPROC, glUniform1i, true);
|
||||
GET(PFNGLUNIFORM1FPROC, glUniform1f, true);
|
||||
GET(PFNGLUNIFORM1FVPROC, glUniform1fv, true);
|
||||
GET(PFNGLUNIFORM2FPROC, glUniform2f, true);
|
||||
GET(PFNGLUNIFORM3FPROC, glUniform3f, true);
|
||||
GET(PFNGLUNIFORM4FPROC, glUniform4f, true);
|
||||
GET(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers, true);
|
||||
GET(PFNGLGENBUFFERSPROC, glGenBuffers, true);
|
||||
GET(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D, true);
|
||||
GET(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers, true);
|
||||
GET(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer, true);
|
||||
GET(PFNGLBINDBUFFERPROC, glBindBuffer, true);
|
||||
GET(PFNGLBUFFERDATAPROC, glBufferData, true);
|
||||
GET(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage, true);
|
||||
GET(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer, true);
|
||||
GET(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus, true);
|
||||
GET(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers, true);
|
||||
GET(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers, true);
|
||||
GET(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer, true);
|
||||
GET(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray, true);
|
||||
GET(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray, true);
|
||||
GET(PFNGLUNIFORMMATRIX4FVARBPROC, glUniformMatrix4fv, true);
|
||||
GET(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation, true);
|
||||
GET(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D, true);
|
||||
GET(PFNGLGETSHADERIVPROC, glGetShaderiv, true);
|
||||
GET(PFNGLGETPROGRAMIVPROC, glGetProgramiv, true);
|
||||
GET(PFNGLDELETESHADERPROC, glDeleteShader, true);
|
||||
GET(PFNGLDELETEBUFFERSPROC, glDeleteBuffers, true);
|
||||
GET(PFNGLDELETEPROGRAMPROC, glDeleteProgram, true);
|
||||
GET(PFNGLDETACHSHADERPROC, glDetachShader, true);
|
||||
GET(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog, true);
|
||||
GET(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog, true);
|
||||
|
||||
// Stuff we can live without:
|
||||
GET(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray, false);
|
||||
GET(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays, false);
|
||||
GET(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays, false);
|
||||
GET(PFNGLBLITFRAMEBUFFERPROC, glBlitFramebuffer, false);
|
||||
GET(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC, glRenderbufferStorageMultisample,
|
||||
false);
|
||||
|
||||
#undef GET
|
||||
#endif // BA_OSTYPE_WINDOWS
|
||||
|
||||
// So that our window comes up nice and black.
|
||||
// FIXME should just make the window's blanking color black.
|
||||
|
||||
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
// Not needed here.
|
||||
#else
|
||||
|
||||
#if BA_SDL2_BUILD
|
||||
// Gonna wait and see if if still need this.
|
||||
#else
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif // BA_SDL2_BUILD
|
||||
|
||||
#endif // IOS/ANDROID
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
void GLContext::SetVSync(bool enable) {
|
||||
assert(InMainThread());
|
||||
|
||||
#if BA_OSTYPE_MACOS
|
||||
CGLContextObj context = CGLGetCurrentContext();
|
||||
BA_PRECONDITION(context);
|
||||
GLint sync = enable;
|
||||
CGLSetParameter(context, kCGLCPSwapInterval, &sync);
|
||||
#else
|
||||
|
||||
#endif // BA_OSTYPE_MACOS
|
||||
}
|
||||
|
||||
GLContext::~GLContext() {
|
||||
if (!InMainThread()) {
|
||||
Log("Error: GLContext dying in non-graphics thread");
|
||||
}
|
||||
#if BA_SDL2_BUILD
|
||||
|
||||
#if BA_RIFT_BUILD
|
||||
// (in rift we only have a window in 2d mode)
|
||||
if (!IsVRMode()) {
|
||||
BA_PRECONDITION_LOG(sdl_window_);
|
||||
}
|
||||
#else // BA_RIFT_MODE
|
||||
BA_PRECONDITION_LOG(sdl_window_);
|
||||
#endif // BA_RIFT_BUILD
|
||||
|
||||
if (sdl_window_) {
|
||||
SDL_DestroyWindow(sdl_window_);
|
||||
sdl_window_ = nullptr;
|
||||
}
|
||||
#elif BA_SDL_BUILD
|
||||
BA_PRECONDITION_LOG(surface_);
|
||||
if (surface_) {
|
||||
SDL_FreeSurface(surface_);
|
||||
surface_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto GLErrorToString(GLenum err) -> std::string {
|
||||
switch (err) {
|
||||
case GL_NO_ERROR:
|
||||
return "GL_NO_ERROR";
|
||||
case GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM";
|
||||
case GL_INVALID_VALUE:
|
||||
return "GL_INVALID_VALUE";
|
||||
case GL_INVALID_OPERATION:
|
||||
return "GL_INVALID_OPERATION";
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "GL_OUT_OF_MEMORY";
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
default:
|
||||
return std::to_string(err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_ENABLE_OPENGL
|
||||
201
src/ballistica/graphics/gl/gl_sys.h
Normal file
201
src/ballistica/graphics/gl/gl_sys.h
Normal file
@ -0,0 +1,201 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_GL_GL_SYS_H_
|
||||
#define BALLISTICA_GRAPHICS_GL_GL_SYS_H_
|
||||
|
||||
#if BA_ENABLE_OPENGL
|
||||
|
||||
#if !BA_OSTYPE_WINDOWS
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
|
||||
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
|
||||
#if BA_USE_ES3_INCLUDES
|
||||
#include <GLES3/gl3.h>
|
||||
#include <GLES3/gl3ext.h>
|
||||
#else
|
||||
#if BA_SDL_BUILD
|
||||
#include <SDL/SDL.h> // needed for ios?...
|
||||
#include <SDL/SDL_opengles2.h>
|
||||
#else
|
||||
// FIXME: According to https://developer.android.com/ndk/guides/stable_apis
|
||||
// we can always link against ES3.1 now that we're API 21+, so we shouldn't
|
||||
// need our funky stubs and function lookups anymore.
|
||||
// (though we'll still need to check for availability of 3.x features)
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#endif // BA_SDL_BUILD
|
||||
#endif // BA_USE_ES3_INCLUDES
|
||||
|
||||
// looks like these few defines are currently missing on android
|
||||
// (s3tc works on some nvidia hardware)
|
||||
#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
|
||||
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
|
||||
#endif
|
||||
#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
||||
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
|
||||
#endif
|
||||
|
||||
#else // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
|
||||
#if BA_SDL2_BUILD
|
||||
#include <SDL_opengl.h>
|
||||
#elif BA_SDL_BUILD // BA_SDL2_BUILD
|
||||
#define NO_SDL_GLEXT
|
||||
#include <SDL_opengl.h>
|
||||
#endif // BA_SDL2_BUILD
|
||||
|
||||
#if BA_OSTYPE_MACOS
|
||||
#include <OpenGL/glext.h>
|
||||
#endif // BA_OSTYPE_MACOS
|
||||
|
||||
#endif // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/platform/min_sdl.h"
|
||||
|
||||
#if BA_OSTYPE_ANDROID
|
||||
extern PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT;
|
||||
#endif
|
||||
|
||||
#if BA_OSTYPE_WINDOWS
|
||||
#ifndef WGL_EXT_swap_control
|
||||
#define WGL_EXT_swap_control 1
|
||||
typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
|
||||
typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(VOID); // NOLINT
|
||||
#endif // WGL_EXT_swap_control
|
||||
extern PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ;
|
||||
extern PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
|
||||
glGetFramebufferAttachmentParameteriv;
|
||||
extern PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate;
|
||||
extern PFNGLACTIVETEXTUREPROC glActiveTexture;
|
||||
extern PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB;
|
||||
extern PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB;
|
||||
extern PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB;
|
||||
extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
|
||||
extern PFNGLCREATEPROGRAMPROC glCreateProgram;
|
||||
extern PFNGLCREATESHADERPROC glCreateShader;
|
||||
extern PFNGLSHADERSOURCEPROC glShaderSource;
|
||||
extern PFNGLCOMPILESHADERPROC glCompileShader;
|
||||
extern PFNGLLINKPROGRAMPROC glLinkProgram;
|
||||
extern PFNGLGETINFOLOGARBPROC glGetInfoLogARB;
|
||||
extern PFNGLATTACHSHADERPROC glAttachShader;
|
||||
extern PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram;
|
||||
extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
|
||||
extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
|
||||
extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
|
||||
extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
|
||||
extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
|
||||
extern PFNGLUNIFORM1IPROC glUniform1i;
|
||||
extern PFNGLUNIFORM1FPROC glUniform1f;
|
||||
extern PFNGLUNIFORM1FVPROC glUniform1fv;
|
||||
extern PFNGLUNIFORM2FPROC glUniform2f;
|
||||
extern PFNGLUNIFORM3FPROC glUniform3f;
|
||||
extern PFNGLUNIFORM4FPROC glUniform4f;
|
||||
extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
|
||||
extern PFNGLGENBUFFERSPROC glGenBuffers;
|
||||
extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
|
||||
extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
|
||||
extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
|
||||
extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
|
||||
extern PFNGLBINDBUFFERPROC glBindBuffer;
|
||||
extern PFNGLBUFFERDATAPROC glBufferData;
|
||||
extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
|
||||
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample;
|
||||
extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
|
||||
extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
|
||||
extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
|
||||
extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
|
||||
extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
|
||||
extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
|
||||
extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
|
||||
extern PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv;
|
||||
extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
|
||||
extern PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D;
|
||||
extern PFNGLGETSHADERIVPROC glGetShaderiv;
|
||||
extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
|
||||
extern PFNGLDELETESHADERPROC glDeleteShader;
|
||||
extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
|
||||
extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
|
||||
extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
|
||||
extern PFNGLDETACHSHADERPROC glDetachShader;
|
||||
extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
|
||||
extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
|
||||
#endif // BA_OSTYPE_WINDOWS
|
||||
|
||||
#ifndef GL_NV_texture_rectangle
|
||||
#define GL_TEXTURE_RECTANGLE_NV 0x84F5
|
||||
#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6
|
||||
#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7
|
||||
#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8
|
||||
#endif
|
||||
#ifndef GL_NV_texture_rectangle
|
||||
#define GL_NV_texture_rectangle 1
|
||||
#endif
|
||||
|
||||
// Support for gl object debug labeling.
|
||||
#if BA_OSTYPE_IOS_TVOS
|
||||
#define GL_LABEL_OBJECT(type, obj, label) glLabelObjectEXT(type, obj, 0, label)
|
||||
#define GL_PUSH_GROUP_MARKER(label) glPushGroupMarkerEXT(0, label)
|
||||
#define GL_POP_GROUP_MARKER() glPopGroupMarkerEXT()
|
||||
#else
|
||||
#define GL_LABEL_OBJECT(type, obj, label) ((void)0)
|
||||
#define GL_PUSH_GROUP_MARKER(label) ((void)0)
|
||||
#define GL_POP_GROUP_MARKER() ((void)0)
|
||||
#endif
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto GLErrorToString(GLenum err) -> std::string;
|
||||
|
||||
// Container for OpenGL rendering context data.
|
||||
class GLContext {
|
||||
public:
|
||||
GLContext(int target_res_x, int target_res_y, bool fullScreen);
|
||||
~GLContext();
|
||||
auto res_x() const -> int { return res_x_; }
|
||||
auto res_y() const -> int { return res_y_; }
|
||||
auto pixel_density() const -> float { return pixel_density_; }
|
||||
void SetVSync(bool enable);
|
||||
|
||||
// Currently no surface/window in this case.
|
||||
#if BA_SDL2_BUILD
|
||||
auto sdl_window() const -> SDL_Window* {
|
||||
assert(sdl_window_);
|
||||
return sdl_window_;
|
||||
}
|
||||
#elif BA_SDL_BUILD // BA_SDL2_BUILD
|
||||
SDL_Surface* sdl_screen_surface() const {
|
||||
assert(surface_);
|
||||
return surface_;
|
||||
}
|
||||
#endif // BA_SDL2_BUILD
|
||||
|
||||
private:
|
||||
#if BA_SDL2_BUILD
|
||||
SDL_Window* sdl_window_{};
|
||||
SDL_GLContext sdl_gl_context_{};
|
||||
#endif // BA_SDL2_BUILD
|
||||
bool fullscreen_{};
|
||||
int res_x_{};
|
||||
int res_y_{};
|
||||
float pixel_density_{1.0f};
|
||||
#if BA_SDL_BUILD && !BA_SDL2_BUILD
|
||||
SDL_Surface* surface_{};
|
||||
#endif
|
||||
}; // GLContext
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_ENABLE_OPENGL
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_GL_GL_SYS_H_
|
||||
6617
src/ballistica/graphics/gl/renderer_gl.cc
Normal file
6617
src/ballistica/graphics/gl/renderer_gl.cc
Normal file
File diff suppressed because it is too large
Load Diff
263
src/ballistica/graphics/gl/renderer_gl.h
Normal file
263
src/ballistica/graphics/gl/renderer_gl.h
Normal file
@ -0,0 +1,263 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_GL_RENDERER_GL_H_
|
||||
#define BALLISTICA_GRAPHICS_GL_RENDERER_GL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
#if BA_ENABLE_OPENGL
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/graphics/gl/gl_sys.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// for now lets not go above 8 since that's what the iPhone 3gs has..
|
||||
// ...haha perhaps should revisit this
|
||||
constexpr int kMaxGLTexUnitsUsed = 5;
|
||||
|
||||
class RendererGL : public Renderer {
|
||||
class FakeVertexArrayObject;
|
||||
class TextureDataGL;
|
||||
class ModelDataGL;
|
||||
class MeshDataGL;
|
||||
class MeshDataSimpleSplitGL;
|
||||
class MeshDataObjectSplitGL;
|
||||
class MeshDataSimpleFullGL;
|
||||
class MeshDataDualTextureFullGL;
|
||||
class MeshDataSmokeFullGL;
|
||||
class MeshDataSpriteGL;
|
||||
class RenderTargetGL;
|
||||
class FramebufferObjectGL;
|
||||
class ShaderGL;
|
||||
class FragmentShaderGL;
|
||||
class VertexShaderGL;
|
||||
class ProgramGL;
|
||||
class SimpleProgramGL;
|
||||
class ObjectProgramGL;
|
||||
class SmokeProgramGL;
|
||||
class BlurProgramGL;
|
||||
class ShieldProgramGL;
|
||||
class PostProcessProgramGL;
|
||||
class SpriteProgramGL;
|
||||
|
||||
public:
|
||||
RendererGL();
|
||||
~RendererGL() override;
|
||||
void Unload() override;
|
||||
void Load() override;
|
||||
void PostLoad() override;
|
||||
|
||||
// our vertex attrs
|
||||
enum VertexAttr {
|
||||
kVertexAttrPosition,
|
||||
kVertexAttrUV,
|
||||
kVertexAttrNormal,
|
||||
kVertexAttrErode,
|
||||
kVertexAttrColor,
|
||||
kVertexAttrSize,
|
||||
kVertexAttrDiffuse,
|
||||
kVertexAttrUV2,
|
||||
kVertexAttrCount
|
||||
};
|
||||
|
||||
void CheckCapabilities() override;
|
||||
auto GetAutoGraphicsQuality() -> GraphicsQuality override;
|
||||
auto GetAutoTextureQuality() -> TextureQuality override;
|
||||
#if BA_OSTYPE_ANDROID
|
||||
std::string GetAutoAndroidRes() override;
|
||||
#endif // BA_OSTYPE_ANDROID
|
||||
|
||||
protected:
|
||||
void DrawDebug() override;
|
||||
void CheckForErrors() override;
|
||||
void GenerateCameraBufferBlurPasses() override;
|
||||
void FlipCullFace() override;
|
||||
void SetDepthRange(float min, float max) override;
|
||||
void SetDepthWriting(bool enable) override;
|
||||
void SetDepthTesting(bool enable) override;
|
||||
void SetDrawAtEqualDepth(bool enable) override;
|
||||
auto NewScreenRenderTarget() -> RenderTarget* override;
|
||||
auto NewFramebufferRenderTarget(int width, int height, bool linear_interp,
|
||||
bool depth, bool texture,
|
||||
bool depth_is_texture, bool high_quality,
|
||||
bool msaa, bool alpha)
|
||||
-> RenderTarget* override;
|
||||
auto NewModelData(const ModelData& model) -> ModelRendererData* override;
|
||||
auto NewTextureData(const TextureData& texture)
|
||||
-> TextureRendererData* override;
|
||||
auto NewMeshData(MeshDataType type, MeshDrawType drawType)
|
||||
-> MeshRendererData* override;
|
||||
void DeleteMeshData(MeshRendererData* data, MeshDataType type) override;
|
||||
void ProcessRenderCommandBuffer(RenderCommandBuffer* buffer,
|
||||
const RenderPass& pass,
|
||||
RenderTarget* render_target) override;
|
||||
void BlitBuffer(RenderTarget* src, RenderTarget* dst, bool depth,
|
||||
bool linear_interpolation, bool force_shader_mode,
|
||||
bool invalidate_source) override;
|
||||
void UpdateMeshes(
|
||||
const std::vector<Object::Ref<MeshDataClientHandle> >& meshes,
|
||||
const std::vector<int8_t>& index_sizes,
|
||||
const std::vector<Object::Ref<MeshBufferBase> >& buffers) override;
|
||||
void PushGroupMarker(const char* label) override;
|
||||
void PopGroupMarker() override;
|
||||
auto IsMSAAEnabled() const -> bool override;
|
||||
void InvalidateFramebuffer(bool color, bool depth,
|
||||
bool target_read_framebuffer) override;
|
||||
void VREyeRenderBegin() override;
|
||||
void CardboardDisableScissor() override;
|
||||
void CardboardEnableScissor() override;
|
||||
void RenderFrameDefEnd() override;
|
||||
|
||||
#if BA_VR_BUILD
|
||||
void VRSyncRenderStates() override;
|
||||
#endif // BA_VR_BUILD
|
||||
|
||||
// TEMP
|
||||
auto current_vertex_array() const -> GLuint { return current_vertex_array_; }
|
||||
|
||||
private:
|
||||
void CheckFunkyDepthIssue();
|
||||
auto GetMSAASamplesForFramebuffer(int width, int height) -> int;
|
||||
void UpdateMSAAEnabled() override;
|
||||
void CheckGLExtensions();
|
||||
void UpdateVignetteTex(bool force) override;
|
||||
void StandardPostProcessSetup(PostProcessProgramGL* p,
|
||||
const RenderPass& pass);
|
||||
void SyncGLState();
|
||||
void RetainShader(ProgramGL* p);
|
||||
void SetViewport(GLint x, GLint y, GLsizei width, GLsizei height);
|
||||
void UseProgram(ProgramGL* p);
|
||||
auto GetActiveProgram() const -> ProgramGL* {
|
||||
assert(current_program_);
|
||||
return current_program_;
|
||||
}
|
||||
void SetDoubleSided(bool d);
|
||||
void ScissorPush(const Rect& rIn, RenderTarget* render_target);
|
||||
void ScissorPop(RenderTarget* render_target);
|
||||
void BindVertexArray(GLuint v);
|
||||
|
||||
// Note: This is only for use when VAOs aren't supported.
|
||||
void SetVertexAttribArrayEnabled(GLuint i, bool enabled);
|
||||
void BindTexture(GLuint type, const TextureData* t, GLuint tex_unit = 0);
|
||||
void BindTexture(GLuint type, GLuint tex, GLuint tex_unit = 0);
|
||||
void BindTextureUnit(uint32_t tex_unit);
|
||||
void BindFramebuffer(GLuint fb);
|
||||
void BindArrayBuffer(GLuint b);
|
||||
void SetBlend(bool b);
|
||||
void SetBlendPremult(bool b);
|
||||
millisecs_t dof_update_time_{};
|
||||
std::vector<Object::Ref<FramebufferObjectGL> > blur_buffers_;
|
||||
bool supports_depth_textures_{};
|
||||
bool first_extension_check_{true};
|
||||
bool is_tegra_4_{};
|
||||
bool is_tegra_k1_{};
|
||||
bool is_recent_adreno_{};
|
||||
bool is_adreno_{};
|
||||
bool enable_msaa_{};
|
||||
float last_cam_buffer_width_{};
|
||||
float last_cam_buffer_height_{};
|
||||
int last_blur_res_count_{};
|
||||
float vignette_tex_outer_r_{};
|
||||
float vignette_tex_outer_g_{};
|
||||
float vignette_tex_outer_b_{};
|
||||
float vignette_tex_inner_r_{};
|
||||
float vignette_tex_inner_g_{};
|
||||
float vignette_tex_inner_b_{};
|
||||
float depth_range_min_{};
|
||||
float depth_range_max_{};
|
||||
bool draw_at_equal_depth_{};
|
||||
bool depth_writing_enabled_{};
|
||||
bool depth_testing_enabled_{};
|
||||
bool data_loaded_{};
|
||||
bool draw_front_{};
|
||||
GLuint screen_framebuffer_{};
|
||||
bool got_screen_framebuffer_{};
|
||||
GLuint random_tex_{};
|
||||
GLuint vignette_tex_{};
|
||||
GraphicsQuality vignette_quality_{};
|
||||
std::vector<std::unique_ptr<ProgramGL> > shaders_;
|
||||
GLint viewport_x_{};
|
||||
GLint viewport_y_{};
|
||||
GLint viewport_width_{};
|
||||
GLint viewport_height_{};
|
||||
SimpleProgramGL* simple_color_prog_{};
|
||||
SimpleProgramGL* simple_tex_prog_{};
|
||||
SimpleProgramGL* simple_tex_dtest_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_flatness_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_shadow_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_shadow_flatness_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_glow_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_glow_maskuv2_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_colorized_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_colorized2_prog_{};
|
||||
SimpleProgramGL* simple_tex_mod_colorized2_masked_prog_{};
|
||||
ObjectProgramGL* obj_prog_{};
|
||||
ObjectProgramGL* obj_transparent_prog_{};
|
||||
ObjectProgramGL* obj_lightshad_transparent_prog_{};
|
||||
ObjectProgramGL* obj_refl_prog_{};
|
||||
ObjectProgramGL* obj_refl_worldspace_prog_{};
|
||||
ObjectProgramGL* obj_refl_transparent_prog_{};
|
||||
ObjectProgramGL* obj_refl_add_transparent_prog_{};
|
||||
ObjectProgramGL* obj_lightshad_prog_{};
|
||||
ObjectProgramGL* obj_lightshad_worldspace_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_worldspace_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_colorize_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_colorize2_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_add_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_add_colorize_prog_{};
|
||||
ObjectProgramGL* obj_refl_lightshad_add_colorize2_prog_{};
|
||||
SmokeProgramGL* smoke_prog_{};
|
||||
SmokeProgramGL* smoke_overlay_prog_{};
|
||||
SpriteProgramGL* sprite_prog_{};
|
||||
SpriteProgramGL* sprite_camalign_prog_{};
|
||||
SpriteProgramGL* sprite_camalign_overlay_prog_{};
|
||||
BlurProgramGL* blur_prog_{};
|
||||
ShieldProgramGL* shield_prog_{};
|
||||
PostProcessProgramGL* postprocess_prog_{};
|
||||
PostProcessProgramGL* postprocess_eyes_prog_{};
|
||||
PostProcessProgramGL* postprocess_distort_prog_{};
|
||||
static auto GetFunkyDepthIssue() -> bool;
|
||||
static auto GetDrawsShieldsFunny() -> bool;
|
||||
static bool funky_depth_issue_set_;
|
||||
static bool funky_depth_issue_;
|
||||
static bool draws_shields_funny_set_;
|
||||
static bool draws_shields_funny_;
|
||||
#if BA_OSTYPE_ANDROID
|
||||
static bool is_speedy_android_device_;
|
||||
static bool is_extra_speedy_android_device_;
|
||||
#endif // BA_OSTYPE_ANDROID
|
||||
ProgramGL* current_program_{};
|
||||
bool double_sided_{};
|
||||
std::vector<Rect> scissor_rects_;
|
||||
GLuint current_vertex_array_{};
|
||||
bool vertex_attrib_arrays_enabled_[kVertexAttrCount]{};
|
||||
int active_tex_unit_{};
|
||||
int active_framebuffer_{};
|
||||
int active_array_buffer_{};
|
||||
int bound_textures_2d_[kMaxGLTexUnitsUsed]{};
|
||||
int bound_textures_cube_map_[kMaxGLTexUnitsUsed]{};
|
||||
bool blend_{};
|
||||
bool blend_premult_{};
|
||||
std::unique_ptr<MeshDataSimpleFullGL> screen_mesh_;
|
||||
std::vector<MeshDataSimpleSplitGL*> recycle_mesh_datas_simple_split_;
|
||||
std::vector<MeshDataObjectSplitGL*> recycle_mesh_datas_object_split_;
|
||||
std::vector<MeshDataSimpleFullGL*> recycle_mesh_datas_simple_full_;
|
||||
std::vector<MeshDataDualTextureFullGL*> recycle_mesh_datas_dual_texture_full_;
|
||||
std::vector<MeshDataSmokeFullGL*> recycle_mesh_datas_smoke_full_;
|
||||
std::vector<MeshDataSpriteGL*> recycle_mesh_datas_sprite_;
|
||||
int error_check_counter_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_ENABLE_OPENGL
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_GL_RENDERER_GL_H_
|
||||
1868
src/ballistica/graphics/graphics.cc
Normal file
1868
src/ballistica/graphics/graphics.cc
Normal file
File diff suppressed because it is too large
Load Diff
424
src/ballistica/graphics/graphics.h
Normal file
424
src/ballistica/graphics/graphics.h
Normal file
@ -0,0 +1,424 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_GRAPHICS_H_
|
||||
#define BALLISTICA_GRAPHICS_GRAPHICS_H_
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/math/matrix44f.h"
|
||||
#include "ballistica/math/rect.h"
|
||||
#include "ballistica/math/vector2f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Light/shadow res is divided by this to get pure light res.
|
||||
const int kLightResDiv = 4;
|
||||
|
||||
// How we divide up our z depth spectrum:
|
||||
const float kBackingDepth5 = 1.0f;
|
||||
|
||||
// Background
|
||||
// blit-shapes (with cam buffer)
|
||||
const float kBackingDepth4 = 0.9f;
|
||||
|
||||
// World (without cam buffer) or overlay-3d (with cam buffer)
|
||||
const float kBackingDepth3C = 0.65f;
|
||||
const float kBackingDepth3B = 0.4f;
|
||||
const float kBackingDepth3 = 0.15f;
|
||||
|
||||
// Overlay-3d (without cam buffer) / overlay(vr)
|
||||
const float kBackingDepth2C = 0.147f;
|
||||
const float kBackingDepth2B = 0.143f;
|
||||
const float kBackingDepth2 = 0.14f;
|
||||
|
||||
// Overlay(non-vr) // cover (vr)
|
||||
const float kBackingDepth1B = 0.01f;
|
||||
const float kBackingDepth1 = 0.0f;
|
||||
|
||||
const float kShadowNeutral = 0.5f;
|
||||
|
||||
// Client class for graphics operations (used from the game thread).
|
||||
class Graphics {
|
||||
public:
|
||||
Graphics();
|
||||
virtual ~Graphics();
|
||||
|
||||
static auto IsShaderTransparent(ShadingType c) -> bool;
|
||||
static auto CubeMapFromReflectionType(ReflectionType reflection_type)
|
||||
-> SystemCubeMapTextureID;
|
||||
|
||||
// Given a string, return a reflection type.
|
||||
static auto ReflectionTypeFromString(const std::string& s) -> ReflectionType;
|
||||
|
||||
// ..and the opposite.
|
||||
static auto StringFromReflectionType(ReflectionType reflectionType)
|
||||
-> std::string;
|
||||
|
||||
auto Reset() -> void;
|
||||
auto BuildAndPushFrameDef() -> void;
|
||||
|
||||
virtual auto ApplyCamera(FrameDef* frame_def) -> void;
|
||||
|
||||
// Called when the GraphicsServer's screen configuration changes.
|
||||
auto ScreenResize(float virtual_width, float virtual_height,
|
||||
float physical_width, float physical_height) -> void;
|
||||
|
||||
// Called when the GraphicsServer has sent us a frame-def for deletion.
|
||||
auto ReturnCompletedFrameDef(FrameDef* frame_def) -> void;
|
||||
|
||||
auto screen_pixel_width() const -> float { return res_x_; }
|
||||
auto screen_pixel_height() const -> float { return res_y_; }
|
||||
|
||||
// Return the size of the virtual screen. This value should always
|
||||
// be used for interface positioning, etc.
|
||||
auto screen_virtual_width() const -> float { return res_x_virtual_; }
|
||||
auto screen_virtual_height() const -> float { return res_y_virtual_; }
|
||||
|
||||
auto ClearScreenMessageTranslations() -> void;
|
||||
|
||||
// Given a point in space, returns the shadow density that should be drawn
|
||||
// into the shadow pass. Does this belong somewhere else?
|
||||
auto GetShadowDensity(float x, float y, float z) -> float;
|
||||
|
||||
static auto GetSafeColor(float* r, float* g, float* b,
|
||||
float target_intensity = 0.6f) -> void;
|
||||
|
||||
// Print a message to the on-screen list.
|
||||
auto AddScreenMessage(const std::string& msg,
|
||||
const Vector3f& color = Vector3f{1, 1, 1},
|
||||
bool top = false, Texture* texture = nullptr,
|
||||
Texture* tint_texture = nullptr,
|
||||
const Vector3f& tint = Vector3f{1, 1, 1},
|
||||
const Vector3f& tint2 = Vector3f{1, 1, 1}) -> void;
|
||||
|
||||
// Fade the local screen in or out over the given time period.
|
||||
auto FadeScreen(bool to, millisecs_t time, PyObject* endcall) -> void;
|
||||
|
||||
static auto DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) -> void;
|
||||
|
||||
// Ways to add a few simple component types quickly.
|
||||
// (uses particle rendering for efficient batches).
|
||||
auto DrawBlotch(const Vector3f& pos, float size, float r, float g, float b,
|
||||
float a) -> void {
|
||||
DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a);
|
||||
}
|
||||
|
||||
auto DrawBlotchSoft(const Vector3f& pos, float size, float r, float g,
|
||||
float b, float a) -> void {
|
||||
DoDrawBlotch(&blotch_soft_indices_, &blotch_soft_verts_, pos, size, r, g, b,
|
||||
a);
|
||||
}
|
||||
|
||||
// Draw a soft blotch on objects; not terrain.
|
||||
auto DrawBlotchSoftObj(const Vector3f& pos, float size, float r, float g,
|
||||
float b, float a) -> void {
|
||||
DoDrawBlotch(&blotch_soft_obj_indices_, &blotch_soft_obj_verts_, pos, size,
|
||||
r, g, b, a);
|
||||
}
|
||||
|
||||
// Enable progress bar drawing locally.
|
||||
auto EnableProgressBar(bool fade_in) -> void;
|
||||
|
||||
auto camera() -> Camera* { return camera_.get(); }
|
||||
auto ToggleManualCamera() -> void;
|
||||
auto LocalCameraShake(float intensity) -> void;
|
||||
auto ToggleDebugDraw() -> void;
|
||||
auto debug_info_display() const -> bool { return debug_info_display_; }
|
||||
auto ToggleDebugInfoDisplay() -> void;
|
||||
auto SetGyroEnabled(bool enable) -> void;
|
||||
auto floor_reflection() const -> bool {
|
||||
assert(InGameThread());
|
||||
return floor_reflection_;
|
||||
}
|
||||
auto set_floor_reflection(bool val) -> void {
|
||||
assert(InGameThread());
|
||||
floor_reflection_ = val;
|
||||
}
|
||||
auto set_shadow_offset(const Vector3f& val) -> void {
|
||||
assert(InGameThread());
|
||||
shadow_offset_ = val;
|
||||
}
|
||||
auto set_shadow_scale(float x, float y) -> void {
|
||||
assert(InGameThread());
|
||||
shadow_scale_.x = x;
|
||||
shadow_scale_.y = y;
|
||||
}
|
||||
auto set_shadow_ortho(bool o) -> void {
|
||||
assert(InGameThread());
|
||||
shadow_ortho_ = o;
|
||||
}
|
||||
auto tint() -> const Vector3f& { return tint_; }
|
||||
auto set_tint(const Vector3f& val) -> void {
|
||||
assert(InGameThread());
|
||||
tint_ = val;
|
||||
}
|
||||
|
||||
auto set_ambient_color(const Vector3f& val) -> void {
|
||||
assert(InGameThread());
|
||||
ambient_color_ = val;
|
||||
}
|
||||
auto set_vignette_outer(const Vector3f& val) -> void {
|
||||
assert(InGameThread());
|
||||
vignette_outer_ = val;
|
||||
}
|
||||
auto set_vignette_inner(const Vector3f& val) -> void {
|
||||
assert(InGameThread());
|
||||
vignette_inner_ = val;
|
||||
}
|
||||
auto shadow_offset() const -> const Vector3f& {
|
||||
assert(InGameThread());
|
||||
return shadow_offset_;
|
||||
}
|
||||
auto shadow_scale() const -> const Vector2f& {
|
||||
assert(InGameThread());
|
||||
return shadow_scale_;
|
||||
}
|
||||
auto tint() const -> const Vector3f& {
|
||||
assert(InGameThread());
|
||||
return tint_;
|
||||
}
|
||||
auto ambient_color() const -> const Vector3f& {
|
||||
assert(InGameThread());
|
||||
return ambient_color_;
|
||||
}
|
||||
auto vignette_outer() const -> const Vector3f& {
|
||||
assert(InGameThread());
|
||||
return vignette_outer_;
|
||||
}
|
||||
auto vignette_inner() const -> const Vector3f& {
|
||||
assert(InGameThread());
|
||||
return vignette_inner_;
|
||||
}
|
||||
auto shadow_ortho() const -> bool {
|
||||
assert(InGameThread());
|
||||
return shadow_ortho_;
|
||||
}
|
||||
auto SetShadowRange(float lower_bottom, float lower_top, float upper_bottom,
|
||||
float upper_top) -> void;
|
||||
auto ReleaseFadeEndCommand() -> void;
|
||||
auto set_show_fps(bool val) -> void { show_fps_ = val; }
|
||||
|
||||
// FIXME - move to graphics_server
|
||||
auto set_tv_border(bool val) -> void {
|
||||
assert(InGameThread());
|
||||
tv_border_ = val;
|
||||
}
|
||||
auto tv_border() const -> bool {
|
||||
assert(InGameThread());
|
||||
return tv_border_;
|
||||
}
|
||||
|
||||
// Nodes that draw flat stuff into the overlay pass should query this z value
|
||||
// for where to draw in z.
|
||||
auto overlay_node_z_depth() -> float {
|
||||
fetched_overlay_node_z_depth_ = true;
|
||||
return overlay_node_z_depth_;
|
||||
}
|
||||
|
||||
// This should be called before/after drawing each node to keep the value
|
||||
// incrementing.
|
||||
auto PreNodeDraw() -> void { fetched_overlay_node_z_depth_ = false; }
|
||||
auto PostNodeDraw() -> void {
|
||||
if (fetched_overlay_node_z_depth_) {
|
||||
overlay_node_z_depth_ *= 0.99f;
|
||||
}
|
||||
}
|
||||
|
||||
auto accel() const -> const Vector3f& { return accel_pos_; }
|
||||
auto tilt() const -> const Vector3f& { return tilt_pos_; }
|
||||
|
||||
auto PixelToVirtualX(float x) const -> float {
|
||||
if (tv_border_) {
|
||||
// In this case, 0 to 1 in physical coords maps to -0.05f to 1.05f in
|
||||
// virtual.
|
||||
return (-0.5f * kTVBorder) * res_x_virtual_
|
||||
+ (1.0f + kTVBorder) * res_x_virtual_ * (x / res_x_);
|
||||
}
|
||||
return x * (res_x_virtual_ / res_x_);
|
||||
}
|
||||
auto PixelToVirtualY(float y) const -> float {
|
||||
if (tv_border_) {
|
||||
// In this case, 0 to 1 in physical coords maps to -0.05f to 1.05f in
|
||||
// virtual.
|
||||
return (-0.5f * kTVBorder) * res_y_virtual_
|
||||
+ (1.0f + kTVBorder) * res_y_virtual_ * (y / res_y_);
|
||||
}
|
||||
return y * (res_y_virtual_ / res_y_);
|
||||
}
|
||||
auto supports_high_quality_graphics() const -> bool {
|
||||
assert(has_supports_high_quality_graphics_value_);
|
||||
return supports_high_quality_graphics_;
|
||||
}
|
||||
auto SetSupportsHighQualityGraphics(bool s) -> void;
|
||||
auto has_supports_high_quality_graphics_value() const -> bool {
|
||||
return has_supports_high_quality_graphics_value_;
|
||||
}
|
||||
auto set_internal_components_inited(bool val) -> void {
|
||||
internal_components_inited_ = val;
|
||||
}
|
||||
auto set_gyro_vals(const Vector3f& vals) -> void { gyro_vals_ = vals; }
|
||||
// auto draw_overlay_bounds() const -> bool { return draw_overlay_bounds_; }
|
||||
// auto set_draw_overlay_bounds(bool val) -> void { draw_overlay_bounds_ =
|
||||
// val; }
|
||||
auto show_net_info() const -> bool { return show_net_info_; }
|
||||
auto set_show_net_info(bool val) -> void { show_net_info_ = val; }
|
||||
auto debug_graph_1() const -> NetGraph* { return debug_graph_1_.get(); }
|
||||
auto debug_graph_2() const -> NetGraph* { return debug_graph_2_.get(); }
|
||||
|
||||
// Used by meshes.
|
||||
auto AddMeshDataCreate(MeshData* d) -> void;
|
||||
auto AddMeshDataDestroy(MeshData* d) -> void;
|
||||
|
||||
// For debugging: ensures that only transparent or opaque components
|
||||
// are submitted while enabled.
|
||||
auto drawing_transparent_only() const -> bool {
|
||||
return drawing_transparent_only_;
|
||||
}
|
||||
auto set_drawing_transparent_only(bool val) -> void {
|
||||
drawing_transparent_only_ = val;
|
||||
}
|
||||
|
||||
auto drawing_opaque_only() const -> bool { return drawing_opaque_only_; }
|
||||
auto set_drawing_opaque_only(bool val) -> void { drawing_opaque_only_ = val; }
|
||||
|
||||
// Handle testing values from _ba.value_test()
|
||||
virtual auto ValueTest(const std::string& arg, double* absval,
|
||||
double* deltaval, double* outval) -> bool;
|
||||
virtual auto DrawUI(FrameDef* frame_def) -> void;
|
||||
virtual auto DrawWorld(Session* session, FrameDef* frame_def) -> void;
|
||||
|
||||
auto set_camera_shake_disabled(bool disabled) -> void {
|
||||
camera_shake_disabled_ = disabled;
|
||||
}
|
||||
auto camera_shake_disabled() const { return camera_shake_disabled_; }
|
||||
auto set_camera_gyro_explicitly_disabled(bool disabled) -> void {
|
||||
camera_gyro_explicitly_disabled_ = disabled;
|
||||
}
|
||||
|
||||
private:
|
||||
class ScreenMessageEntry;
|
||||
auto DrawBoxingGlovesTest(FrameDef* frame_def) -> void;
|
||||
auto DrawBlotches(FrameDef* frame_def) -> void;
|
||||
auto DrawCursor(RenderPass* pass, millisecs_t real_time) -> void;
|
||||
auto DrawFades(FrameDef* frame_def, millisecs_t real_time) -> void;
|
||||
auto DrawDebugBuffers(RenderPass* pass) -> void;
|
||||
auto WaitForRendererToExist() -> void;
|
||||
|
||||
auto UpdateAndDrawProgressBar(FrameDef* frame_def, millisecs_t real_time)
|
||||
-> void;
|
||||
auto DoDrawBlotch(std::vector<uint16_t>* indices,
|
||||
std::vector<VertexSprite>* verts, const Vector3f& pos,
|
||||
float size, float r, float g, float b, float a) -> void;
|
||||
auto GetEmptyFrameDef() -> FrameDef*;
|
||||
auto InitInternalComponents(FrameDef* frame_def) -> void;
|
||||
auto DrawMiscOverlays(RenderPass* pass) -> void;
|
||||
auto DrawLoadDot(RenderPass* pass) -> void;
|
||||
auto ClearFrameDefDeleteList() -> void;
|
||||
auto DrawProgressBar(RenderPass* pass, float opacity) -> void;
|
||||
auto UpdateProgressBarProgress(float target) -> void;
|
||||
auto UpdateGyro(millisecs_t real_time, millisecs_t elapsed) -> void;
|
||||
|
||||
bool drawing_transparent_only_{};
|
||||
bool drawing_opaque_only_{};
|
||||
std::vector<MeshData*> mesh_data_creates_;
|
||||
std::vector<MeshData*> mesh_data_destroys_;
|
||||
bool has_supports_high_quality_graphics_value_{};
|
||||
bool supports_high_quality_graphics_ = false;
|
||||
millisecs_t last_create_frame_def_time_{};
|
||||
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
|
||||
Vector2f shadow_scale_{1.0f, 1.0f};
|
||||
bool shadow_ortho_ = false;
|
||||
Vector3f tint_{1.0f, 1.0f, 1.0f};
|
||||
Vector3f ambient_color_{1.0f, 1.0f, 1.0f};
|
||||
Vector3f vignette_outer_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f vignette_inner_{1.0f, 1.0f, 1.0f};
|
||||
std::vector<FrameDef*> recycle_frame_defs_;
|
||||
millisecs_t last_jitter_update_time_ = 0;
|
||||
Vector3f jitter_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f accel_smoothed_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f accel_smoothed2_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f accel_hi_pass_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f accel_vel_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f accel_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f tilt_smoothed_ = {0.0f, 0.0f, 0.0f};
|
||||
Vector3f tilt_vel_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f tilt_pos_{0.0f, 0.0f, 0.0f};
|
||||
bool gyro_broken_{};
|
||||
float gyro_mag_test_{};
|
||||
bool fetched_overlay_node_z_depth_{};
|
||||
float overlay_node_z_depth_{};
|
||||
bool internal_components_inited_{};
|
||||
Object::Ref<ImageMesh> screen_mesh_;
|
||||
Object::Ref<ImageMesh> progress_bar_bottom_mesh_;
|
||||
Object::Ref<ImageMesh> progress_bar_top_mesh_;
|
||||
Object::Ref<ImageMesh> load_dot_mesh_;
|
||||
Object::Ref<TextGroup> fps_text_group_;
|
||||
Object::Ref<TextGroup> net_info_text_group_;
|
||||
Object::Ref<SpriteMesh> shadow_blotch_mesh_;
|
||||
Object::Ref<SpriteMesh> shadow_blotch_soft_mesh_;
|
||||
Object::Ref<SpriteMesh> shadow_blotch_soft_obj_mesh_;
|
||||
std::string fps_string_;
|
||||
std::string net_info_string_;
|
||||
std::vector<uint16_t> blotch_indices_;
|
||||
std::vector<VertexSprite> blotch_verts_;
|
||||
std::vector<uint16_t> blotch_soft_indices_;
|
||||
std::vector<VertexSprite> blotch_soft_verts_;
|
||||
std::vector<uint16_t> blotch_soft_obj_indices_;
|
||||
std::vector<VertexSprite> blotch_soft_obj_verts_;
|
||||
bool show_fps_{};
|
||||
bool show_net_info_{};
|
||||
bool tv_border_{};
|
||||
bool floor_reflection_{};
|
||||
Object::Ref<NetGraph> debug_graph_1_;
|
||||
Object::Ref<NetGraph> debug_graph_2_;
|
||||
std::mutex frame_def_delete_list_mutex_;
|
||||
std::vector<FrameDef*> frame_def_delete_list_;
|
||||
bool debug_draw_{};
|
||||
bool debug_info_display_{};
|
||||
Object::Ref<Camera> camera_;
|
||||
millisecs_t next_stat_update_time_{};
|
||||
int last_total_frames_rendered_{};
|
||||
int last_fps_{};
|
||||
std::list<ScreenMessageEntry> screen_messages_;
|
||||
std::list<ScreenMessageEntry> screen_messages_top_;
|
||||
bool set_fade_start_on_next_draw_{};
|
||||
millisecs_t fade_start_{};
|
||||
millisecs_t fade_time_{};
|
||||
bool fade_out_{true};
|
||||
Object::Ref<PythonContextCall> fade_end_call_;
|
||||
float fade_{};
|
||||
Vector3f gyro_vals_{0.0f, 0.0, 0.0f};
|
||||
float res_x_{100};
|
||||
float res_y_{100};
|
||||
float res_x_virtual_{100};
|
||||
float res_y_virtual_{100};
|
||||
int progress_bar_loads_{};
|
||||
bool progress_bar_{};
|
||||
bool progress_bar_fade_in_{};
|
||||
millisecs_t progress_bar_end_time_{-9999};
|
||||
float progress_bar_progress_{};
|
||||
millisecs_t last_progress_bar_draw_time_{};
|
||||
millisecs_t last_progress_bar_start_time_{};
|
||||
float screen_gamma_{1.0f};
|
||||
float shadow_lower_bottom_{-4.0f};
|
||||
float shadow_lower_top_{4.0f};
|
||||
float shadow_upper_bottom_{30.0f};
|
||||
float shadow_upper_top_{40.0f};
|
||||
bool hardware_cursor_visible_{};
|
||||
bool camera_shake_disabled_{};
|
||||
bool camera_gyro_explicitly_disabled_{};
|
||||
millisecs_t last_cursor_visibility_event_time_{};
|
||||
int64_t frame_def_count_{1};
|
||||
bool gyro_enabled_{true};
|
||||
millisecs_t last_suppress_gyro_time_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_GRAPHICS_H_
|
||||
765
src/ballistica/graphics/graphics_server.cc
Normal file
765
src/ballistica/graphics/graphics_server.cc
Normal file
@ -0,0 +1,765 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "ballistica/core/thread.h"
|
||||
#include "ballistica/generic/lambda_runnable.h"
|
||||
#include "ballistica/graphics/gl/renderer_gl.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
// FIXME: clear out this conditional stuff.
|
||||
#if BA_SDL_BUILD
|
||||
#include "ballistica/platform/sdl/sdl_app.h"
|
||||
#else
|
||||
#include "ballistica/app/app.h"
|
||||
#endif
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
|
||||
void GraphicsServer::FullscreenCheck() {
|
||||
if (!fullscreen_enabled()) {
|
||||
#if BA_ENABLE_OPENGL
|
||||
SDL_WM_ToggleFullScreen(gl_context_->sdl_screen_surface());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
GraphicsServer::GraphicsServer(Thread* thread) : Module("graphics", thread) {
|
||||
// We're a singleton.
|
||||
assert(g_graphics_server == nullptr);
|
||||
g_graphics_server = this;
|
||||
|
||||
// For janky old non-event-push mode, just fall back on a timer for rendering.
|
||||
if (!g_platform->IsEventPushMode()) {
|
||||
render_timer_ = NewThreadTimer(1000 / 60, true,
|
||||
NewLambdaRunnable([this] { TryRender(); }));
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsServer::~GraphicsServer() { assert(g_graphics); }
|
||||
|
||||
void GraphicsServer::SetRenderHold() {
|
||||
assert(InGraphicsThread());
|
||||
render_hold_++;
|
||||
}
|
||||
|
||||
void GraphicsServer::SetFrameDef(FrameDef* framedef) {
|
||||
// Note: we're just setting the framedef directly here
|
||||
// even though this gets called from the game thread.
|
||||
// Ideally it would seem we should push these to our thread
|
||||
// event list, but currently we spin-lock waiting for new
|
||||
// frames to appear which would prevent that from working;
|
||||
// we would need to change that code.
|
||||
assert(frame_def_ == nullptr);
|
||||
frame_def_ = framedef;
|
||||
}
|
||||
|
||||
auto GraphicsServer::GetRenderFrameDef() -> FrameDef* {
|
||||
assert(InGraphicsThread());
|
||||
millisecs_t real_time = GetRealTime();
|
||||
|
||||
if (!renderer_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If the app says it's minimized minimized, don't do anything.
|
||||
// (on iOS we'll get shut down if we make GL calls in this state, etc)
|
||||
if (g_app->paused()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Do some incremental loading every time we try to render.
|
||||
g_media->RunPendingGraphicsLoads();
|
||||
|
||||
// Spin and wait for a short bit for a frame_def to appear. If it does, we
|
||||
// grab it, render it, and also message the game thread to start generating
|
||||
// another one.
|
||||
while (true) {
|
||||
if (frame_def_) {
|
||||
FrameDef* frame_def = frame_def_;
|
||||
frame_def_ = nullptr;
|
||||
|
||||
// Tell the game thread we're ready for the next frame_def so it can start
|
||||
// building it while we render this one.
|
||||
g_game->PushFrameDefRequest();
|
||||
return frame_def;
|
||||
}
|
||||
|
||||
// If there's no frame_def for us, sleep for a bit and wait for it.
|
||||
// But if we've been waiting for too long, give up.
|
||||
// On some platforms such as android, this frame will still get flipped
|
||||
// whether we draw in it or not, so we really dont want to not draw if we
|
||||
// can help it.
|
||||
millisecs_t t = GetRealTime() - real_time;
|
||||
if (t >= 1000) {
|
||||
break; // Fail.
|
||||
}
|
||||
Platform::SleepMS(2);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Runs any mesh updates contained in the frame-def.
|
||||
void GraphicsServer::RunFrameDefMeshUpdates(FrameDef* frame_def) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
// Run any mesh-data creates/destroys included with this frame_def.
|
||||
for (auto&& i : frame_def->mesh_data_creates()) {
|
||||
assert(i != nullptr);
|
||||
i->iterator_ = mesh_datas_.insert(mesh_datas_.end(), i);
|
||||
i->Load(renderer_);
|
||||
}
|
||||
for (auto&& i : frame_def->mesh_data_destroys()) {
|
||||
assert(i != nullptr);
|
||||
i->Unload(renderer_);
|
||||
mesh_datas_.erase(i->iterator_);
|
||||
}
|
||||
}
|
||||
|
||||
// Renders shadow passes and other common parts of a frame_def.
|
||||
void GraphicsServer::PreprocessRenderFrameDef(FrameDef* frame_def) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
// Now let the renderer do any preprocess passes (shadows, etc).
|
||||
renderer_->PreprocessFrameDef(frame_def);
|
||||
}
|
||||
|
||||
// Does the default drawing to the screen, either from the left or right stereo
|
||||
// eye or in mono.
|
||||
void GraphicsServer::DrawRenderFrameDef(FrameDef* frame_def, int eye) {
|
||||
renderer_->RenderFrameDef(frame_def);
|
||||
}
|
||||
|
||||
// Clean up the frame_def once done drawing it.
|
||||
void GraphicsServer::FinishRenderFrameDef(FrameDef* frame_def) {
|
||||
renderer_->FinishFrameDef(frame_def);
|
||||
|
||||
// Let the app know a frame render is complete (it may need to do a swap/etc).
|
||||
g_app->DidFinishRenderingFrame(frame_def);
|
||||
}
|
||||
|
||||
void GraphicsServer::TryRender() {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
if (FrameDef* frame_def = GetRenderFrameDef()) {
|
||||
// Note: we always run mesh updates contained in the framedef
|
||||
// even if we don't actually render it.
|
||||
// (Hmm this seems flaky; will TryRender always get called
|
||||
// for each FrameDef?... perhaps we should separate mesh updates
|
||||
// from FrameDefs? Or change our logic so that frame-defs *always* get
|
||||
// rendered.
|
||||
RunFrameDefMeshUpdates(frame_def);
|
||||
|
||||
// Only actually render if we have a screen and aren't in a hold.
|
||||
auto target = renderer()->screen_render_target();
|
||||
if (target != nullptr && render_hold_ == 0) {
|
||||
PreprocessRenderFrameDef(frame_def);
|
||||
DrawRenderFrameDef(frame_def);
|
||||
FinishRenderFrameDef(frame_def);
|
||||
}
|
||||
|
||||
// Send this frame_def back to the game thread for deletion.
|
||||
g_graphics->ReturnCompletedFrameDef(frame_def);
|
||||
}
|
||||
}
|
||||
|
||||
// Reload all media (for debugging/benchmarking purposes).
|
||||
void GraphicsServer::ReloadMedia() {
|
||||
assert(InMainThread());
|
||||
|
||||
// Immediately unload all renderer data here in this thread.
|
||||
if (renderer_) {
|
||||
g_media->UnloadRendererBits(true, true);
|
||||
}
|
||||
|
||||
// Set a render-hold so we ignore all frame_defs up until the point at which
|
||||
// we receive the corresponding remove-hold.
|
||||
// (At which point subsequent frame-defs will be be progress-bar frame_defs so
|
||||
// we won't hitch if we actually render them.)
|
||||
assert(g_graphics_server);
|
||||
SetRenderHold();
|
||||
|
||||
// Now tell the game thread to kick off loads for everything, flip on
|
||||
// progress bar drawing, and then tell the graphics thread to stop ignoring
|
||||
// frame-defs.
|
||||
g_game->PushCall([this] {
|
||||
g_media->MarkAllMediaForLoad();
|
||||
g_graphics->EnableProgressBar(false);
|
||||
PushRemoveRenderHoldCall();
|
||||
});
|
||||
}
|
||||
|
||||
// Call when renderer context has been lost.
|
||||
void GraphicsServer::RebuildLostContext() {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
if (!renderer_) {
|
||||
Log("Error: No renderer on GraphicsServer::_rebuildContext.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark our context as lost so the renderer knows to not try and tear things
|
||||
// down itself.
|
||||
set_renderer_context_lost(true);
|
||||
|
||||
// Unload all texture and model data here in the render thread.
|
||||
g_media->UnloadRendererBits(true, true);
|
||||
|
||||
// Also unload dynamic meshes.
|
||||
for (auto&& i : mesh_datas_) {
|
||||
i->Unload(renderer_);
|
||||
}
|
||||
|
||||
// And other internal renderer stuff.
|
||||
renderer_->Unload();
|
||||
|
||||
set_renderer_context_lost(false);
|
||||
|
||||
// Now reload.
|
||||
renderer_->Load();
|
||||
|
||||
// Also (re)load all dynamic meshes.
|
||||
for (auto&& i : mesh_datas_) {
|
||||
i->Load(renderer_);
|
||||
}
|
||||
|
||||
renderer_->ScreenSizeChanged();
|
||||
|
||||
// Set a render-hold so we ignore all frame_defs up until the point at which
|
||||
// we receive the corresponding remove-hold.
|
||||
// (At which point subsequent frame-defs will be be progress-bar frame_defs so
|
||||
// we won't hitch if we actually render them.)
|
||||
SetRenderHold();
|
||||
|
||||
// Now tell the game thread to kick off loads for everything, flip on progress
|
||||
// bar drawing, and then tell the graphics thread to stop ignoring frame-defs.
|
||||
g_game->PushCall([this] {
|
||||
g_media->MarkAllMediaForLoad();
|
||||
g_graphics->EnableProgressBar(false);
|
||||
PushRemoveRenderHoldCall();
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::SetScreen(bool fullscreen, int width, int height,
|
||||
TextureQuality texture_quality_requested,
|
||||
GraphicsQuality graphics_quality_requested,
|
||||
const std::string& android_res) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
// If we know what we support, filter out requests we don't support
|
||||
// (will keep us from rebuilding contexts due to our requested and actual
|
||||
// values not lining up).
|
||||
if (g_graphics->has_supports_high_quality_graphics_value()) {
|
||||
if (!g_graphics->supports_high_quality_graphics()
|
||||
&& (graphics_quality_requested == GraphicsQuality::kHigh
|
||||
|| graphics_quality_requested == GraphicsQuality::kHigher)) {
|
||||
graphics_quality_requested = GraphicsQuality::kMedium;
|
||||
}
|
||||
}
|
||||
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && BA_SDL_BUILD
|
||||
bool create_fullscreen_check_timer = false;
|
||||
#endif
|
||||
|
||||
bool do_toggle_fs = false;
|
||||
bool do_set_existing_fs = false;
|
||||
|
||||
if (HeadlessMode()) {
|
||||
// We don't actually make or update a renderer in headless, but we
|
||||
// still need to set our list of supported textures types/etc. to avoid
|
||||
// complaints.
|
||||
std::list<TextureCompressionType> c_types;
|
||||
SetTextureCompressionTypes(c_types);
|
||||
quality_requested_ = quality_actual_ = GraphicsQuality::kLow;
|
||||
graphics_quality_set_ = true;
|
||||
texture_quality_requested_ = texture_quality_actual_ = TextureQuality::kLow;
|
||||
texture_quality_set_ = true;
|
||||
} else {
|
||||
// OK - starting in SDL2 we never pass in specific resolution requests..
|
||||
// we request fullscreen-windows for full-screen situations and that's it.
|
||||
// (otherwise we may wind up with huge windows due to passing in desktop
|
||||
// resolutions and retina wonkiness)
|
||||
width = 800;
|
||||
height = 600;
|
||||
|
||||
// We should never have to recreate the context after the initial time on
|
||||
// our modern builds.
|
||||
bool need_full_context_rebuild = (!renderer_);
|
||||
bool need_renderer_reload;
|
||||
|
||||
// We need a full renderer reload if quality values have changed.
|
||||
need_renderer_reload =
|
||||
((texture_quality_requested_ != texture_quality_requested)
|
||||
|| (quality_requested_ != graphics_quality_requested)
|
||||
|| !texture_quality_set() || !graphics_quality_set());
|
||||
|
||||
// This stuff requires a full context rebuild.
|
||||
if (need_full_context_rebuild || need_renderer_reload) {
|
||||
HandleFullContextScreenRebuild(need_full_context_rebuild, fullscreen,
|
||||
width, height, graphics_quality_requested,
|
||||
texture_quality_requested);
|
||||
// On mac, we let window save/restore handle our fullscreen restoring for
|
||||
// us. However if document restore is turned off we'll start windowed on
|
||||
// every launch. So if we're trying to make a fullscreen setup, lets
|
||||
// check after a short delay to make sure we have it, and run a
|
||||
// full-screen-toggle ourself if not.
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && BA_SDL_BUILD
|
||||
if (fullscreen) {
|
||||
create_fullscreen_check_timer = true;
|
||||
}
|
||||
#endif // BA_OSTYPE_MACOS
|
||||
|
||||
} else {
|
||||
// on SDL2 builds we can just set fullscreen on the existing window; no
|
||||
// need for a context rebuild
|
||||
#if BA_SDL2_BUILD
|
||||
do_set_existing_fs = true;
|
||||
#else
|
||||
// On our old custom SDL1.2 mac build, fullscreen toggling winds up here.
|
||||
// this doesn't require a context rebuild either.
|
||||
if (fullscreen != fullscreen_enabled()) {
|
||||
do_toggle_fs = true;
|
||||
}
|
||||
#endif // BA_SDL2_BUILD
|
||||
}
|
||||
|
||||
HandlePushAndroidRes(android_res);
|
||||
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && BA_SDL_BUILD
|
||||
if (create_fullscreen_check_timer) {
|
||||
NewThreadTimer(1000, false,
|
||||
NewLambdaRunnable([this] { FullscreenCheck(); }));
|
||||
}
|
||||
#endif // BA_OSTYPE_MACOS
|
||||
|
||||
HandleFullscreenToggling(do_set_existing_fs, do_toggle_fs, fullscreen);
|
||||
}
|
||||
|
||||
// The first time we complete setting up our screen, we send a message
|
||||
// back to the game thread to complete the init process.. (they can't start
|
||||
// loading graphics and things until we have our context set up so we know
|
||||
// what types of textures to load, etc)
|
||||
if (!initial_screen_created_) {
|
||||
initial_screen_created_ = true;
|
||||
g_game->PushInitialScreenCreatedCall();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsServer::HandleFullContextScreenRebuild(
|
||||
bool need_full_context_rebuild, bool fullscreen, int width, int height,
|
||||
GraphicsQuality graphics_quality_requested,
|
||||
TextureQuality texture_quality_requested) {
|
||||
// Unload renderer-specific data (display-lists, internal textures, etc)
|
||||
if (renderer_) {
|
||||
// Unload all textures and models.. these will be reloaded as-needed
|
||||
// automatically for the new context..
|
||||
g_media->UnloadRendererBits(true, true);
|
||||
|
||||
// Also unload all dynamic meshes.
|
||||
for (auto&& i : mesh_datas_) {
|
||||
i->Unload(renderer_);
|
||||
}
|
||||
|
||||
// And all internal renderer stuff.
|
||||
renderer_->Unload();
|
||||
}
|
||||
|
||||
// Handle screen/context recreation.
|
||||
if (need_full_context_rebuild) {
|
||||
// On mac we store the values we *want* separate from those we get.. (so
|
||||
// we know when our request has changed; not our result).
|
||||
#if !(BA_OSTYPE_MACOS && BA_XCODE_BUILD)
|
||||
fullscreen_enabled_ = fullscreen;
|
||||
#endif
|
||||
|
||||
target_res_x_ = static_cast<float>(width);
|
||||
target_res_y_ = static_cast<float>(height);
|
||||
|
||||
#if BA_ENABLE_OPENGL
|
||||
gl_context_ = std::make_unique<GLContext>(width, height, fullscreen);
|
||||
res_x_ = static_cast<float>(gl_context_->res_x());
|
||||
res_y_ = static_cast<float>(gl_context_->res_y());
|
||||
#endif
|
||||
|
||||
UpdateVirtualScreenRes();
|
||||
|
||||
// Inform the game thread of the latest values.
|
||||
g_game->PushScreenResizeCall(res_x_virtual_, res_y_virtual_, res_x_,
|
||||
res_y_);
|
||||
}
|
||||
|
||||
if (!renderer_) {
|
||||
#if BA_ENABLE_OPENGL
|
||||
renderer_ = new RendererGL();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Make sure we've done this first so we can properly set auto values and
|
||||
// whatnot.
|
||||
renderer_->CheckCapabilities();
|
||||
|
||||
// Update graphics quality.
|
||||
quality_requested_ = graphics_quality_requested;
|
||||
if (quality_requested_ == GraphicsQuality::kAuto) {
|
||||
quality_actual_ = renderer_->GetAutoGraphicsQuality();
|
||||
} else {
|
||||
quality_actual_ = quality_requested_;
|
||||
}
|
||||
|
||||
// If we don't support high quality graphics, make sure we're no higher than
|
||||
// medium.
|
||||
BA_PRECONDITION(g_graphics->has_supports_high_quality_graphics_value());
|
||||
if (!g_graphics->supports_high_quality_graphics()
|
||||
&& quality_actual_ >= GraphicsQuality::kHigh) {
|
||||
quality_actual_ = GraphicsQuality::kMedium;
|
||||
}
|
||||
graphics_quality_set_ = true;
|
||||
|
||||
// Update texture quality.
|
||||
texture_quality_requested_ = texture_quality_requested;
|
||||
if (texture_quality_requested_ == TextureQuality::kAuto) {
|
||||
texture_quality_actual_ = renderer_->GetAutoTextureQuality();
|
||||
} else {
|
||||
texture_quality_actual_ = texture_quality_requested_;
|
||||
}
|
||||
texture_quality_set_ = true;
|
||||
|
||||
// Ok we've got our qualities figured out; now load/update the renderer.
|
||||
renderer_->Load();
|
||||
|
||||
// Also (re)load all existing dynamic meshes.
|
||||
for (auto&& i : mesh_datas_) {
|
||||
i->Load(renderer_);
|
||||
}
|
||||
renderer_->ScreenSizeChanged();
|
||||
renderer_->PostLoad();
|
||||
|
||||
// Set a render-hold so we ignore all frame_defs up until the point at which
|
||||
// we receive the corresponding remove-hold.
|
||||
// (At which point subsequent frame-defs will be be progress-bar frame_defs
|
||||
// so we won't hitch if we actually render them.)
|
||||
SetRenderHold();
|
||||
|
||||
// Now tell the game thread to kick off loads for everything, flip on
|
||||
// progress bar drawing, and then tell the graphics thread to stop ignoring
|
||||
// frame-defs.
|
||||
g_game->PushCall([this] {
|
||||
g_media->MarkAllMediaForLoad();
|
||||
g_graphics->set_internal_components_inited(false);
|
||||
g_graphics->EnableProgressBar(false);
|
||||
PushRemoveRenderHoldCall();
|
||||
});
|
||||
}
|
||||
|
||||
// Given physical res, calculate virtual res.
|
||||
void GraphicsServer::CalcVirtualRes(float* x, float* y) {
|
||||
float x_in = (*x);
|
||||
float y_in = (*y);
|
||||
if ((*x) / (*y) > static_cast<float>(kBaseVirtualResX)
|
||||
/ static_cast<float>(kBaseVirtualResY)) {
|
||||
(*y) = kBaseVirtualResY;
|
||||
(*x) = (*y) * (x_in / y_in);
|
||||
} else {
|
||||
*x = kBaseVirtualResX;
|
||||
*y = (*x) * (y_in / x_in);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsServer::UpdateVirtualScreenRes() {
|
||||
assert(InGraphicsThread());
|
||||
// In vr mode our virtual res is independent of our screen size.
|
||||
// (since it gets drawn to an overlay)
|
||||
if (IsVRMode()) {
|
||||
res_x_virtual_ = kBaseVirtualResX;
|
||||
res_y_virtual_ = kBaseVirtualResY;
|
||||
} else {
|
||||
res_x_virtual_ = res_x_;
|
||||
res_y_virtual_ = res_y_;
|
||||
CalcVirtualRes(&res_x_virtual_, &res_y_virtual_);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsServer::VideoResize(float h, float v) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
if (target_res_x_ == h && target_res_y_ == v) {
|
||||
return;
|
||||
}
|
||||
|
||||
target_res_x_ = h;
|
||||
target_res_y_ = v;
|
||||
res_x_ = h;
|
||||
res_y_ = v;
|
||||
UpdateVirtualScreenRes();
|
||||
|
||||
// Inform the game thread of the latest values.
|
||||
g_game->PushScreenResizeCall(res_x_virtual_, res_y_virtual_, res_x_, res_y_);
|
||||
if (renderer_) {
|
||||
renderer_->ScreenSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Shouldn't have android-specific code in here.
|
||||
void GraphicsServer::HandlePushAndroidRes(const std::string& android_res) {
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// We push android res to the java layer here. We don't actually worry
|
||||
// about screen-size-changed callbacks and whatnot, since those will happen
|
||||
// automatically once things actually change. We just want to be sure that
|
||||
// we have a renderer so we can calc what our auto res should be.
|
||||
assert(renderer_);
|
||||
std::string fin_res;
|
||||
if (android_res == "Auto") {
|
||||
fin_res = renderer_->GetAutoAndroidRes();
|
||||
} else {
|
||||
fin_res = android_res;
|
||||
}
|
||||
g_platform->AndroidSetResString(fin_res);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsServer::HandleFullscreenToggling(bool do_set_existing_fs,
|
||||
bool do_toggle_fs,
|
||||
bool fullscreen) {
|
||||
if (do_set_existing_fs) {
|
||||
#if BA_SDL2_BUILD
|
||||
bool rift_vr_mode = false;
|
||||
#if BA_RIFT_BUILD
|
||||
if (IsVRMode()) {
|
||||
rift_vr_mode = true;
|
||||
}
|
||||
#endif // BA_RIFT_BUILD
|
||||
if (explicit_bool(!rift_vr_mode)) {
|
||||
#if BA_OSTYPE_IOS_TVOS
|
||||
set_fullscreen_enabled(true);
|
||||
|
||||
#else // BA_OSTYPE_IOS_TVOS
|
||||
uint32_t fullscreen_flag = SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
SDL_SetWindowFullscreen(gl_context_->sdl_window(),
|
||||
fullscreen ? fullscreen_flag : 0);
|
||||
|
||||
// Ideally this should be driven by OS events and not just explicitly by
|
||||
// us (so, for instance, if someone presses fullscreen on mac we'd know
|
||||
// we've gone into fullscreen). But this works for now.
|
||||
set_fullscreen_enabled(fullscreen);
|
||||
#endif // BA_OSTYPE_IOS_TVOS
|
||||
}
|
||||
#endif // BA_SDL2_BUILD
|
||||
} else if (do_toggle_fs) {
|
||||
// If we're doing a fullscreen-toggle, we need to do it after coming out of
|
||||
// sync mode (because the toggle triggers sync-mode itself).
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
|
||||
#if BA_ENABLE_OPENGL
|
||||
SDL_WM_ToggleFullScreen(gl_context_->sdl_screen_surface());
|
||||
#endif
|
||||
#endif // macos && xcode_build
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsServer::SetTextureCompressionTypes(
|
||||
const std::list<TextureCompressionType>& types) {
|
||||
texture_compression_types_ = 0; // Reset.
|
||||
for (auto&& i : types) {
|
||||
texture_compression_types_ |= (0x01u << (static_cast<uint32_t>(i)));
|
||||
}
|
||||
texture_compression_types_set_ = true;
|
||||
}
|
||||
|
||||
void GraphicsServer::SetOrthoProjection(float left, float right, float bottom,
|
||||
float top, float nearval,
|
||||
float farval) {
|
||||
float tx = -((right + left) / (right - left));
|
||||
float ty = -((top + bottom) / (top - bottom));
|
||||
float tz = -((farval + nearval) / (farval - nearval));
|
||||
|
||||
projection_matrix_.m[0] = 2.0f / (right - left);
|
||||
projection_matrix_.m[4] = 0.0f;
|
||||
projection_matrix_.m[8] = 0.0f;
|
||||
projection_matrix_.m[12] = tx;
|
||||
|
||||
projection_matrix_.m[1] = 0.0f;
|
||||
projection_matrix_.m[5] = 2.0f / (top - bottom);
|
||||
projection_matrix_.m[9] = 0.0f;
|
||||
projection_matrix_.m[13] = ty;
|
||||
|
||||
projection_matrix_.m[2] = 0.0f;
|
||||
projection_matrix_.m[6] = 0.0f;
|
||||
projection_matrix_.m[10] = -2.0f / (farval - nearval);
|
||||
projection_matrix_.m[14] = tz;
|
||||
|
||||
projection_matrix_.m[3] = 0.0f;
|
||||
projection_matrix_.m[7] = 0.0f;
|
||||
projection_matrix_.m[11] = 0.0f;
|
||||
projection_matrix_.m[15] = 1.0f;
|
||||
|
||||
model_view_projection_matrix_dirty_ = true;
|
||||
projection_matrix_state_++;
|
||||
}
|
||||
|
||||
void GraphicsServer::SetCamera(const Vector3f& eye, const Vector3f& target,
|
||||
const Vector3f& up_vector) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
// Reset the modelview stack.
|
||||
model_view_stack_.clear();
|
||||
|
||||
auto forward = (target - eye).Normalized();
|
||||
auto side = Vector3f::Cross(forward, up_vector).Normalized();
|
||||
Vector3f up = Vector3f::Cross(side, forward);
|
||||
|
||||
//------------------
|
||||
model_view_matrix_.m[0] = side.x;
|
||||
model_view_matrix_.m[4] = side.y;
|
||||
model_view_matrix_.m[8] = side.z;
|
||||
model_view_matrix_.m[12] = 0.0f;
|
||||
//------------------
|
||||
model_view_matrix_.m[1] = up.x;
|
||||
model_view_matrix_.m[5] = up.y;
|
||||
model_view_matrix_.m[9] = up.z;
|
||||
model_view_matrix_.m[13] = 0.0f;
|
||||
//------------------
|
||||
model_view_matrix_.m[2] = -forward.x;
|
||||
model_view_matrix_.m[6] = -forward.y;
|
||||
model_view_matrix_.m[10] = -forward.z;
|
||||
model_view_matrix_.m[14] = 0.0f;
|
||||
//------------------
|
||||
model_view_matrix_.m[3] = model_view_matrix_.m[7] = model_view_matrix_.m[11] =
|
||||
0.0f;
|
||||
model_view_matrix_.m[15] = 1.0f;
|
||||
//------------------
|
||||
model_view_matrix_ =
|
||||
Matrix44fTranslate(-eye.x, -eye.y, -eye.z) * model_view_matrix_;
|
||||
view_world_matrix_ = model_view_matrix_.Inverse();
|
||||
|
||||
model_view_projection_matrix_dirty_ = true;
|
||||
model_world_matrix_dirty_ = true;
|
||||
|
||||
cam_pos_ = eye;
|
||||
cam_target_ = target;
|
||||
cam_pos_state_++;
|
||||
cam_orient_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
void GraphicsServer::UpdateCamOrientMatrix() {
|
||||
assert(InGraphicsThread());
|
||||
if (cam_orient_matrix_dirty_) {
|
||||
cam_orient_matrix_ = kMatrix44fIdentity;
|
||||
Vector3f to_cam = cam_pos_ - cam_target_;
|
||||
to_cam.Normalize();
|
||||
Vector3f world_up(0, 1, 0);
|
||||
Vector3f side = Vector3f::Cross(world_up, to_cam);
|
||||
side.Normalize();
|
||||
Vector3f up = Vector3f::Cross(side, to_cam);
|
||||
cam_orient_matrix_.m[0] = side.x;
|
||||
cam_orient_matrix_.m[1] = side.y;
|
||||
cam_orient_matrix_.m[2] = side.z;
|
||||
cam_orient_matrix_.m[4] = to_cam.x;
|
||||
cam_orient_matrix_.m[5] = to_cam.y;
|
||||
cam_orient_matrix_.m[6] = to_cam.z;
|
||||
cam_orient_matrix_.m[8] = up.x;
|
||||
cam_orient_matrix_.m[9] = up.y;
|
||||
cam_orient_matrix_.m[10] = up.z;
|
||||
cam_orient_matrix_.m[3] = cam_orient_matrix_.m[7] =
|
||||
cam_orient_matrix_.m[11] = cam_orient_matrix_.m[12] =
|
||||
cam_orient_matrix_.m[13] = cam_orient_matrix_.m[14] = 0.0f;
|
||||
cam_orient_matrix_.m[15] = 1.0f;
|
||||
cam_orient_matrix_state_++;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark PushCalls
|
||||
|
||||
void GraphicsServer::PushSetScreenCall(bool fullscreen, int width, int height,
|
||||
TextureQuality texture_quality,
|
||||
GraphicsQuality graphics_quality,
|
||||
const std::string& android_res) {
|
||||
PushCall([=] {
|
||||
SetScreen(fullscreen, width, height, texture_quality, graphics_quality,
|
||||
android_res);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::PushReloadMediaCall() {
|
||||
PushCall([this] { ReloadMedia(); });
|
||||
}
|
||||
|
||||
void GraphicsServer::PushSetScreenGammaCall(float gamma) {
|
||||
PushCall([this, gamma] {
|
||||
assert(InGraphicsThread());
|
||||
if (!renderer_) {
|
||||
return;
|
||||
}
|
||||
renderer_->set_screen_gamma(gamma);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::PushSetScreenPixelScaleCall(float pixel_scale) {
|
||||
PushCall([this, pixel_scale] {
|
||||
assert(InGraphicsThread());
|
||||
if (!renderer_) {
|
||||
return;
|
||||
}
|
||||
renderer_->set_pixel_scale(pixel_scale);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::PushSetVSyncCall(bool sync, bool auto_sync) {
|
||||
PushCall([this, sync, auto_sync] {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
#if BA_SDL_BUILD
|
||||
|
||||
// Currently only supported for SDLApp.
|
||||
// May want to revisit this later.
|
||||
if (g_buildconfig.sdl_build()) {
|
||||
// Even if we were built with SDL, we may not be running in sdl-app-mode
|
||||
// (for instance, Rift in VR mode). Only do this if we're an sdl app.
|
||||
if (auto app = dynamic_cast<SDLApp*>(g_app)) {
|
||||
v_sync_ = sync;
|
||||
auto_vsync_ = auto_sync;
|
||||
if (gl_context_) {
|
||||
app->SetAutoVSync(auto_vsync_);
|
||||
// Set it directly if not auto...
|
||||
if (!auto_vsync_) {
|
||||
gl_context_->SetVSync(v_sync_);
|
||||
}
|
||||
} else {
|
||||
Log("Error: Got SetVSyncCall with no gl context.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // BA_HEADLESS_BUILD
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::PushComponentUnloadCall(
|
||||
const std::vector<Object::Ref<MediaComponentData>*>& components) {
|
||||
PushCall([this, components] {
|
||||
// Unload all components we were passed.
|
||||
for (auto&& i : components) {
|
||||
(**i).Unload();
|
||||
}
|
||||
// ..and then ship these pointers back to the game thread so it can free the
|
||||
// references.
|
||||
g_game->PushFreeMediaComponentRefsCall(components);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphicsServer::PushRemoveRenderHoldCall() {
|
||||
PushCall([this] {
|
||||
assert(render_hold_);
|
||||
render_hold_--;
|
||||
if (render_hold_ < 0) {
|
||||
Log("Error: RenderHold < 0");
|
||||
render_hold_ = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
334
src/ballistica/graphics/graphics_server.h
Normal file
334
src/ballistica/graphics/graphics_server.h
Normal file
@ -0,0 +1,334 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_GRAPHICS_SERVER_H_
|
||||
#define BALLISTICA_GRAPHICS_GRAPHICS_SERVER_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/core/module.h"
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/math/matrix44f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Runs in the main thread and renders frame_defs shipped to it by the
|
||||
// Graphics
|
||||
class GraphicsServer : public Module {
|
||||
public:
|
||||
explicit GraphicsServer(Thread* thread);
|
||||
auto PushSetScreenGammaCall(float gamma) -> void;
|
||||
auto PushSetScreenPixelScaleCall(float pixel_scale) -> void;
|
||||
auto PushSetVSyncCall(bool sync, bool auto_sync) -> void;
|
||||
auto PushSetScreenCall(bool fullscreen, int width, int height,
|
||||
TextureQuality texture_quality,
|
||||
GraphicsQuality graphics_quality,
|
||||
const std::string& android_res) -> void;
|
||||
auto PushReloadMediaCall() -> void;
|
||||
auto PushRemoveRenderHoldCall() -> void;
|
||||
auto PushComponentUnloadCall(
|
||||
const std::vector<Object::Ref<MediaComponentData>*>& components) -> void;
|
||||
auto SetRenderHold() -> void;
|
||||
|
||||
// Used by the game thread to pass frame-defs to the graphics server
|
||||
// for rendering.
|
||||
auto SetFrameDef(FrameDef* framedef) -> void;
|
||||
|
||||
// returns the next frame_def needing to be rendered, waiting for it to arrive
|
||||
// if necessary. this can return nullptr if no frame_defs come in within a
|
||||
// reasonable amount of time. a frame_def here *must* be rendered and disposed
|
||||
// of using the RenderFrameDef* calls
|
||||
auto GetRenderFrameDef() -> FrameDef*;
|
||||
|
||||
auto RunFrameDefMeshUpdates(FrameDef* frame_def) -> void;
|
||||
|
||||
// renders shadow passes and other common parts of a frame_def
|
||||
auto PreprocessRenderFrameDef(FrameDef* frame_def) -> void;
|
||||
|
||||
// Does the default drawing to the screen, either from the left or right
|
||||
// stereo eye or in mono.
|
||||
auto DrawRenderFrameDef(FrameDef* frame_def, int eye = -1) -> void;
|
||||
|
||||
// Clean up the frame_def once done drawing it.
|
||||
auto FinishRenderFrameDef(FrameDef* frame_def) -> void;
|
||||
|
||||
// Equivalent to calling GetRenderFrameDef() and then preprocess, draw (in
|
||||
// mono), and finish.
|
||||
auto TryRender() -> void;
|
||||
|
||||
// init the modelview matrix to look here
|
||||
auto SetCamera(const Vector3f& eye, const Vector3f& target,
|
||||
const Vector3f& up) -> void;
|
||||
auto SetOrthoProjection(float left, float right, float bottom, float top,
|
||||
float near, float far) -> void;
|
||||
auto ModelViewReset() -> void {
|
||||
model_view_matrix_ = kMatrix44fIdentity;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
model_view_stack_.clear();
|
||||
}
|
||||
auto SetProjectionMatrix(const Matrix44f& p) -> void {
|
||||
projection_matrix_ = p;
|
||||
model_view_projection_matrix_dirty_ = true;
|
||||
projection_matrix_state_++;
|
||||
}
|
||||
auto projection_matrix_state() -> uint32_t {
|
||||
return projection_matrix_state_;
|
||||
}
|
||||
|
||||
auto SetLightShadowProjectionMatrix(const Matrix44f& p) -> void {
|
||||
// This will generally get repeatedly set to the same value
|
||||
// so we can do nothing most of the time.
|
||||
if (p != light_shadow_projection_matrix_) {
|
||||
light_shadow_projection_matrix_ = p;
|
||||
light_shadow_projection_matrix_state_++;
|
||||
}
|
||||
}
|
||||
auto light_shadow_projection_matrix_state() const -> uint32_t {
|
||||
return light_shadow_projection_matrix_state_;
|
||||
}
|
||||
auto light_shadow_projection_matrix() const -> const Matrix44f& {
|
||||
return light_shadow_projection_matrix_;
|
||||
}
|
||||
|
||||
// Returns the modelview * projection matrix.
|
||||
auto GetModelViewProjectionMatrix() -> const Matrix44f& {
|
||||
UpdateModelViewProjectionMatrix();
|
||||
return model_view_projection_matrix_;
|
||||
}
|
||||
|
||||
auto GetModelViewProjectionMatrixState() -> uint32_t {
|
||||
UpdateModelViewProjectionMatrix();
|
||||
return model_view_projection_matrix_state_;
|
||||
}
|
||||
|
||||
auto GetModelWorldMatrix() -> const Matrix44f& {
|
||||
UpdateModelWorldMatrix();
|
||||
return model_world_matrix_;
|
||||
}
|
||||
|
||||
auto GetModelWorldMatrixState() -> uint32_t {
|
||||
UpdateModelWorldMatrix();
|
||||
return model_world_matrix_state_;
|
||||
}
|
||||
|
||||
auto cam_pos() -> const Vector3f& { return cam_pos_; }
|
||||
auto cam_pos_state() -> uint32_t { return cam_pos_state_; }
|
||||
auto GetCamOrientMatrix() -> const Matrix44f& {
|
||||
UpdateCamOrientMatrix();
|
||||
return cam_orient_matrix_;
|
||||
}
|
||||
|
||||
auto GetCamOrientMatrixState() -> uint32_t {
|
||||
UpdateCamOrientMatrix();
|
||||
return cam_orient_matrix_state_;
|
||||
}
|
||||
|
||||
auto model_view_matrix() const -> const Matrix44f& {
|
||||
return model_view_matrix_;
|
||||
}
|
||||
auto SetModelViewMatrix(const Matrix44f& m) -> void {
|
||||
model_view_matrix_ = m;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto projection_matrix() const -> const Matrix44f& {
|
||||
return projection_matrix_;
|
||||
}
|
||||
auto PushTransform() -> void {
|
||||
model_view_stack_.push_back(model_view_matrix_);
|
||||
assert(model_view_stack_.size() < 20);
|
||||
}
|
||||
|
||||
auto PopTransform() -> void {
|
||||
assert(!model_view_stack_.empty());
|
||||
model_view_matrix_ = model_view_stack_.back();
|
||||
model_view_stack_.pop_back();
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto Translate(const Vector3f& t) -> void {
|
||||
model_view_matrix_ = Matrix44fTranslate(t) * model_view_matrix_;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto Rotate(float angle, const Vector3f& axis) -> void {
|
||||
model_view_matrix_ = Matrix44fRotate(axis, angle) * model_view_matrix_;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto MultMatrix(const Matrix44f& m) -> void {
|
||||
model_view_matrix_ = m * model_view_matrix_;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto scale(const Vector3f& s) -> void {
|
||||
model_view_matrix_ = Matrix44fScale(s) * model_view_matrix_;
|
||||
model_view_projection_matrix_dirty_ = model_world_matrix_dirty_ = true;
|
||||
}
|
||||
|
||||
auto RebuildLostContext() -> void;
|
||||
~GraphicsServer() override;
|
||||
|
||||
auto renderer() { return renderer_; }
|
||||
auto quality() const -> GraphicsQuality {
|
||||
assert(graphics_quality_set_);
|
||||
return quality_actual_;
|
||||
}
|
||||
|
||||
auto texture_quality() const -> TextureQuality {
|
||||
assert(texture_quality_set_);
|
||||
return texture_quality_actual_;
|
||||
}
|
||||
|
||||
auto screen_pixel_width() const -> float {
|
||||
assert(InMainThread());
|
||||
return res_x_;
|
||||
}
|
||||
auto screen_pixel_height() const -> float {
|
||||
assert(InMainThread());
|
||||
return res_y_;
|
||||
}
|
||||
|
||||
auto screen_virtual_width() const -> float {
|
||||
assert(InMainThread());
|
||||
return res_x_virtual_;
|
||||
}
|
||||
auto screen_virtual_height() const -> float {
|
||||
assert(InMainThread());
|
||||
return res_y_virtual_;
|
||||
}
|
||||
auto set_tv_border(bool val) -> void {
|
||||
assert(InMainThread());
|
||||
tv_border_ = val;
|
||||
}
|
||||
auto tv_border() const {
|
||||
assert(InMainThread());
|
||||
return tv_border_;
|
||||
}
|
||||
|
||||
auto graphics_quality_set() const { return graphics_quality_set_; }
|
||||
auto texture_quality_set() const { return texture_quality_set_; }
|
||||
|
||||
auto SupportsTextureCompressionType(TextureCompressionType t) const -> bool {
|
||||
assert(texture_compression_types_set_);
|
||||
return ((texture_compression_types_ & (0x01u << static_cast<uint32_t>(t)))
|
||||
!= 0u);
|
||||
}
|
||||
auto SetTextureCompressionTypes(
|
||||
const std::list<TextureCompressionType>& types) -> void;
|
||||
|
||||
auto texture_compression_types_are_set() const {
|
||||
return texture_compression_types_set_;
|
||||
}
|
||||
auto set_renderer_context_lost(bool lost) -> auto {
|
||||
renderer_context_lost_ = lost;
|
||||
}
|
||||
auto renderer_context_lost() const { return renderer_context_lost_; }
|
||||
auto fullscreen_enabled() const { return fullscreen_enabled_; }
|
||||
|
||||
// This doesn't actually toggle fullscreen. It is used to inform the game
|
||||
// when fullscreen changes under it.
|
||||
auto set_fullscreen_enabled(bool fs) -> void { fullscreen_enabled_ = fs; }
|
||||
auto VideoResize(float h, float v) -> void;
|
||||
|
||||
#if BA_ENABLE_OPENGL
|
||||
auto gl_context() const -> GLContext* { return gl_context_.get(); }
|
||||
#endif
|
||||
|
||||
auto graphics_quality_requested() const { return quality_requested_; }
|
||||
auto texture_quality_requested() const { return texture_quality_requested_; }
|
||||
auto renderer() const { return renderer_; }
|
||||
auto initial_screen_created() const { return initial_screen_created_; }
|
||||
|
||||
private:
|
||||
auto HandleFullscreenToggling(bool do_set_existing_fs, bool do_toggle_fs,
|
||||
bool fullscreen) -> void;
|
||||
auto HandlePushAndroidRes(const std::string& android_res) -> void;
|
||||
auto HandleFullContextScreenRebuild(
|
||||
bool need_full_context_rebuild, bool fullscreen, int width, int height,
|
||||
GraphicsQuality graphics_quality_requested,
|
||||
TextureQuality texture_quality_requested) -> void;
|
||||
|
||||
// Update virtual screen dimensions based on the current physical ones.
|
||||
static auto CalcVirtualRes(float* x, float* y) -> void;
|
||||
|
||||
auto UpdateVirtualScreenRes() -> void;
|
||||
auto UpdateCamOrientMatrix() -> void;
|
||||
auto ReloadMedia() -> void;
|
||||
auto UpdateModelViewProjectionMatrix() -> void {
|
||||
if (model_view_projection_matrix_dirty_) {
|
||||
model_view_projection_matrix_ = model_view_matrix_ * projection_matrix_;
|
||||
model_view_projection_matrix_state_++;
|
||||
model_view_projection_matrix_dirty_ = false;
|
||||
}
|
||||
}
|
||||
auto UpdateModelWorldMatrix() -> void {
|
||||
if (model_world_matrix_dirty_) {
|
||||
model_world_matrix_ = model_view_matrix_ * view_world_matrix_;
|
||||
model_world_matrix_state_++;
|
||||
model_world_matrix_dirty_ = false;
|
||||
}
|
||||
}
|
||||
#if BA_ENABLE_OPENGL
|
||||
std::unique_ptr<GLContext> gl_context_;
|
||||
#endif
|
||||
float res_x_{};
|
||||
float res_y_{};
|
||||
float res_x_virtual_{0.0f};
|
||||
float res_y_virtual_{0.0f};
|
||||
bool tv_border_{};
|
||||
bool renderer_context_lost_{};
|
||||
uint32_t texture_compression_types_{};
|
||||
bool texture_compression_types_set_{};
|
||||
TextureQuality texture_quality_requested_{TextureQuality::kLow};
|
||||
TextureQuality texture_quality_actual_{TextureQuality::kLow};
|
||||
GraphicsQuality quality_requested_{GraphicsQuality::kLow};
|
||||
GraphicsQuality quality_actual_{GraphicsQuality::kLow};
|
||||
bool graphics_quality_set_{};
|
||||
bool texture_quality_set_{};
|
||||
bool fullscreen_enabled_{};
|
||||
float target_res_x_{800.0f};
|
||||
float target_res_y_{600.0f};
|
||||
|
||||
Matrix44f model_view_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f view_world_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f projection_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f model_view_projection_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f model_world_matrix_{kMatrix44fIdentity};
|
||||
std::vector<Matrix44f> model_view_stack_;
|
||||
uint32_t projection_matrix_state_{1};
|
||||
uint32_t model_view_projection_matrix_state_{1};
|
||||
uint32_t model_world_matrix_state_{1};
|
||||
bool model_view_projection_matrix_dirty_{true};
|
||||
bool model_world_matrix_dirty_{true};
|
||||
Matrix44f light_shadow_projection_matrix_{kMatrix44fIdentity};
|
||||
uint32_t light_shadow_projection_matrix_state_{1};
|
||||
Vector3f cam_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f cam_target_{0.0f, 0.0f, 0.0f};
|
||||
uint32_t cam_pos_state_{1};
|
||||
Matrix44f cam_orient_matrix_ = kMatrix44fIdentity;
|
||||
uint32_t cam_orient_matrix_state_{1};
|
||||
bool cam_orient_matrix_dirty_{true};
|
||||
std::list<MeshData*> mesh_datas_;
|
||||
bool v_sync_{};
|
||||
bool auto_vsync_{};
|
||||
auto SetScreen(bool fullscreen, int width, int height,
|
||||
TextureQuality texture_quality,
|
||||
GraphicsQuality graphics_quality,
|
||||
const std::string& android_res) -> void;
|
||||
Timer* render_timer_{};
|
||||
Renderer* renderer_{};
|
||||
FrameDef* frame_def_{};
|
||||
bool initial_screen_created_{};
|
||||
int render_hold_{};
|
||||
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
|
||||
void FullscreenCheck();
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_GRAPHICS_SERVER_H_
|
||||
17
src/ballistica/graphics/mesh/image_mesh.cc
Normal file
17
src/ballistica/graphics/mesh/image_mesh.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/mesh/image_mesh.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
const uint16_t kImageMeshIndices[] = {0, 1, 2, 1, 3, 2};
|
||||
const VertexSimpleSplitStatic kImageMeshVerticesStatic[] = {
|
||||
{0, 65535}, {65535, 65535}, {0, 0}, {65535, 0}};
|
||||
|
||||
ImageMesh::ImageMesh() {
|
||||
SetIndexData(Object::New<MeshIndexBuffer16>(6, kImageMeshIndices));
|
||||
SetStaticData(Object::New<MeshBuffer<VertexSimpleSplitStatic> >(
|
||||
4, kImageMeshVerticesStatic));
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
27
src/ballistica/graphics/mesh/image_mesh.h
Normal file
27
src/ballistica/graphics/mesh/image_mesh.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_IMAGE_MESH_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_IMAGE_MESH_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_simple_split.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a mesh set up to draw images
|
||||
class ImageMesh : public MeshIndexedSimpleSplit {
|
||||
public:
|
||||
ImageMesh();
|
||||
void SetPositionAndSize(float x, float y, float z, float width,
|
||||
float height) {
|
||||
VertexSimpleSplitDynamic vdynamic[] = {{x, y, z},
|
||||
{x + width, y, z},
|
||||
{x, y + height, z},
|
||||
{x + width, y + height, z}};
|
||||
SetDynamicData(
|
||||
Object::New<MeshBuffer<VertexSimpleSplitDynamic>>(4, vdynamic));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_IMAGE_MESH_H_
|
||||
46
src/ballistica/graphics/mesh/mesh.h
Normal file
46
src/ballistica/graphics/mesh/mesh.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/graphics/mesh/mesh_data.h"
|
||||
#include "ballistica/graphics/mesh/mesh_data_client_handle.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A user-defined dynamic mesh (unlike a model which is completely static)
|
||||
class Mesh : public Object {
|
||||
public:
|
||||
auto type() const -> MeshDataType { return type_; }
|
||||
auto mesh_data_client_handle() -> Object::Ref<MeshDataClientHandle>& {
|
||||
return mesh_data_client_handle_;
|
||||
}
|
||||
|
||||
// Return whether it is safe to attempt drawing with present data.
|
||||
virtual auto IsValid() const -> bool = 0;
|
||||
auto last_frame_def_num() const -> int64_t { return last_frame_def_num_; }
|
||||
void set_last_frame_def_num(int64_t f) { last_frame_def_num_ = f; }
|
||||
|
||||
protected:
|
||||
explicit Mesh(MeshDataType type,
|
||||
MeshDrawType draw_type = MeshDrawType::kStatic)
|
||||
: valid_(false), type_(type), last_frame_def_num_(0) {
|
||||
mesh_data_client_handle_ =
|
||||
Object::New<MeshDataClientHandle>(new MeshData(type, draw_type));
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t last_frame_def_num_{};
|
||||
MeshDataType type_{};
|
||||
|
||||
// Renderer data for this mesh. We keep this as a shared pointer
|
||||
// so that frame_defs or other things using this mesh can keep it alive
|
||||
// even if we go away.
|
||||
Object::Ref<MeshDataClientHandle> mesh_data_client_handle_;
|
||||
bool valid_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_H_
|
||||
28
src/ballistica/graphics/mesh/mesh_buffer.h
Normal file
28
src/ballistica/graphics/mesh/mesh_buffer.h
Normal file
@ -0,0 +1,28 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer_base.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Buffer for arbitrary mesh data.
|
||||
template <typename T>
|
||||
class MeshBuffer : public MeshBufferBase {
|
||||
public:
|
||||
MeshBuffer() = default;
|
||||
explicit MeshBuffer(size_t initial_size) : elements(initial_size) {}
|
||||
MeshBuffer(size_t initial_size, const T* initial_data)
|
||||
: elements(initial_size) {
|
||||
memcpy(&elements[0], initial_data, initial_size * sizeof(T));
|
||||
}
|
||||
std::vector<T> elements;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_H_
|
||||
21
src/ballistica/graphics/mesh/mesh_buffer_base.h
Normal file
21
src/ballistica/graphics/mesh/mesh_buffer_base.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_BASE_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_BASE_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Buffers used by the game thread to pass indices/vertices/etc. to meshes in
|
||||
// the graphics thread. Note that it is safe to create these in other threads;
|
||||
// you just need to turn off thread-checks until you pass ownership to the game
|
||||
// thread. (or just avoid creating references outside of the game thread)
|
||||
class MeshBufferBase : public Object {
|
||||
public:
|
||||
uint32_t state; // which dynamicState value on the mesh this corresponds to
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_BASE_H_
|
||||
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SIMPLE_FULL_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SIMPLE_FULL_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// just make this a vanilla child class of our template
|
||||
// (simply so we could predeclare this)
|
||||
class MeshBufferVertexSimpleFull : public MeshBuffer<VertexSimpleFull> {
|
||||
using MeshBuffer::MeshBuffer;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SIMPLE_FULL_H_
|
||||
18
src/ballistica/graphics/mesh/mesh_buffer_vertex_smoke_full.h
Normal file
18
src/ballistica/graphics/mesh/mesh_buffer_vertex_smoke_full.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SMOKE_FULL_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SMOKE_FULL_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// just make this a vanilla child class of our template
|
||||
// (simply so we could predeclare this)
|
||||
class MeshBufferVertexSmokeFull : public MeshBuffer<VertexSmokeFull> {
|
||||
using MeshBuffer::MeshBuffer;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SMOKE_FULL_H_
|
||||
18
src/ballistica/graphics/mesh/mesh_buffer_vertex_sprite.h
Normal file
18
src/ballistica/graphics/mesh/mesh_buffer_vertex_sprite.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SPRITE_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SPRITE_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// just make this a vanilla child class of our template
|
||||
// (simply so we could predeclare this)
|
||||
class MeshBufferVertexSprite : public MeshBuffer<VertexSprite> {
|
||||
using MeshBuffer::MeshBuffer;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_BUFFER_VERTEX_SPRITE_H_
|
||||
24
src/ballistica/graphics/mesh/mesh_data.cc
Normal file
24
src/ballistica/graphics/mesh/mesh_data.cc
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_data.h"
|
||||
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void MeshData::Load(Renderer* renderer) {
|
||||
assert(InGraphicsThread());
|
||||
if (!renderer_data_) {
|
||||
renderer_data_ = renderer->NewMeshData(type(), draw_type());
|
||||
}
|
||||
}
|
||||
|
||||
void MeshData::Unload(Renderer* renderer) {
|
||||
assert(InGraphicsThread());
|
||||
if (renderer_data_) {
|
||||
renderer->DeleteMeshData(renderer_data_, type());
|
||||
renderer_data_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
42
src/ballistica/graphics/mesh/mesh_data.h
Normal file
42
src/ballistica/graphics/mesh/mesh_data.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_DATA_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_DATA_H_
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// The portion of a mesh that is owned by the graphics thread.
|
||||
// This contains the renderer-specific data (GL buffers, etc)
|
||||
class MeshData {
|
||||
public:
|
||||
MeshData(MeshDataType type, MeshDrawType draw_type)
|
||||
: type_(type), draw_type_(draw_type) {}
|
||||
virtual ~MeshData() {
|
||||
if (renderer_data_) {
|
||||
Log("Error: MeshData going down with rendererData intact!");
|
||||
}
|
||||
}
|
||||
std::list<MeshData*>::iterator iterator_;
|
||||
auto type() -> MeshDataType { return type_; }
|
||||
auto draw_type() -> MeshDrawType { return draw_type_; }
|
||||
void Load(Renderer* renderer);
|
||||
void Unload(Renderer* renderer);
|
||||
auto renderer_data() const -> MeshRendererData* {
|
||||
assert(renderer_data_);
|
||||
return renderer_data_;
|
||||
}
|
||||
|
||||
private:
|
||||
MeshRendererData* renderer_data_{};
|
||||
MeshDataType type_{};
|
||||
MeshDrawType draw_type_{};
|
||||
BA_DISALLOW_CLASS_COPIES(MeshData);
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_DATA_H_
|
||||
17
src/ballistica/graphics/mesh/mesh_data_client_handle.cc
Normal file
17
src/ballistica/graphics/mesh/mesh_data_client_handle.cc
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_data_client_handle.h"
|
||||
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
MeshDataClientHandle::MeshDataClientHandle(MeshData* d) : mesh_data(d) {
|
||||
g_graphics->AddMeshDataCreate(mesh_data);
|
||||
}
|
||||
|
||||
MeshDataClientHandle::~MeshDataClientHandle() {
|
||||
g_graphics->AddMeshDataDestroy(mesh_data);
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
22
src/ballistica/graphics/mesh/mesh_data_client_handle.h
Normal file
22
src/ballistica/graphics/mesh/mesh_data_client_handle.h
Normal file
@ -0,0 +1,22 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_DATA_CLIENT_HANDLE_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_DATA_CLIENT_HANDLE_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Client-side (game-thread) handle to server-side (graphics-thread) mesh data.
|
||||
// Server-side data will be created when this object is instantiated and
|
||||
// cleared when this object goes down.
|
||||
class MeshDataClientHandle : public Object {
|
||||
public:
|
||||
explicit MeshDataClientHandle(MeshData* d);
|
||||
~MeshDataClientHandle() override;
|
||||
MeshData* mesh_data;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_DATA_CLIENT_HANDLE_H_
|
||||
17
src/ballistica/graphics/mesh/mesh_index_buffer_16.h
Normal file
17
src/ballistica/graphics/mesh/mesh_index_buffer_16.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_16_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_16_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// standard buffer for indices
|
||||
class MeshIndexBuffer16 : public MeshBuffer<uint16_t> {
|
||||
using MeshBuffer::MeshBuffer;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_16_H_
|
||||
17
src/ballistica/graphics/mesh/mesh_index_buffer_32.h
Normal file
17
src/ballistica/graphics/mesh/mesh_index_buffer_32.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_32_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_32_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_buffer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// standard buffer for indices
|
||||
class MeshIndexBuffer32 : public MeshBuffer<uint32_t> {
|
||||
using MeshBuffer::MeshBuffer;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEX_BUFFER_32_H_
|
||||
41
src/ballistica/graphics/mesh/mesh_indexed.h
Normal file
41
src/ballistica/graphics/mesh/mesh_indexed.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_base.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Mesh using indices and vertex data (all either static or dynamic).
|
||||
// Supports both 16 and 32 bit indices.
|
||||
template <typename DATA, MeshDataType T>
|
||||
class MeshIndexed : public MeshIndexedBase {
|
||||
public:
|
||||
explicit MeshIndexed(MeshDrawType draw_type = MeshDrawType::kDynamic)
|
||||
: MeshIndexedBase(T, draw_type) {}
|
||||
void SetData(const Object::Ref<MeshBuffer<DATA>>& data) {
|
||||
assert(!data->elements.empty());
|
||||
data_ = data;
|
||||
data_->state = ++data_state_;
|
||||
}
|
||||
auto data() const -> const Object::Ref<MeshBuffer<DATA>>& { return data_; }
|
||||
|
||||
auto IsValid() const -> bool override {
|
||||
if (!data_.exists() || data_->elements.empty()
|
||||
|| !MeshIndexedBase::IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure our index size covers our element count.
|
||||
return IndexSizeIsValid(data_->elements.size());
|
||||
}
|
||||
|
||||
private:
|
||||
Object::Ref<MeshBuffer<DATA>> data_;
|
||||
uint32_t data_state_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_H_
|
||||
106
src/ballistica/graphics/mesh/mesh_indexed_base.h
Normal file
106
src/ballistica/graphics/mesh/mesh_indexed_base.h
Normal file
@ -0,0 +1,106 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_BASE_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_BASE_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh.h"
|
||||
#include "ballistica/graphics/mesh/mesh_index_buffer_16.h"
|
||||
#include "ballistica/graphics/mesh/mesh_index_buffer_32.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Mesh supporting index data.
|
||||
class MeshIndexedBase : public Mesh {
|
||||
public:
|
||||
explicit MeshIndexedBase(MeshDataType type,
|
||||
MeshDrawType draw_type = MeshDrawType::kDynamic)
|
||||
: Mesh(type, draw_type) {}
|
||||
|
||||
auto index_data_size() const -> int {
|
||||
assert(index_data_size_ != 0);
|
||||
return index_data_size_;
|
||||
}
|
||||
|
||||
void SetIndexData(const Object::Ref<MeshIndexBuffer32>& data) {
|
||||
assert(data.exists() && !data->elements.empty());
|
||||
// unlike vertex data, index data might often remain the same, so lets test
|
||||
// for that and avoid some gl updates..
|
||||
if (index_data_32_.exists()) {
|
||||
assert(data.exists() && index_data_32_.get());
|
||||
if (data->elements == index_data_32_->elements) {
|
||||
return; // just keep our existing one
|
||||
}
|
||||
}
|
||||
index_data_32_ = data;
|
||||
index_data_32_->state = ++index_state_;
|
||||
index_data_size_ = 4;
|
||||
// kill any other index data we have
|
||||
index_data_16_.Clear();
|
||||
}
|
||||
|
||||
void SetIndexData(const Object::Ref<MeshIndexBuffer16>& data) {
|
||||
assert(data.exists() && !data->elements.empty());
|
||||
// unlike vertex data, index data might often remain the same, so lets test
|
||||
// for that and avoid some gl updates..
|
||||
if (index_data_16_.exists()) {
|
||||
assert(index_data_16_.get());
|
||||
if (data->elements == index_data_16_->elements) {
|
||||
return; // just keep our existing one
|
||||
}
|
||||
}
|
||||
// FIXME - we should probably just pass in a strong ref as an arg?...
|
||||
index_data_16_ = data;
|
||||
index_data_16_->state = ++index_state_;
|
||||
index_data_size_ = 2;
|
||||
// kill any other index data we have
|
||||
index_data_32_.Clear();
|
||||
}
|
||||
|
||||
// call this if you have nothing to draw
|
||||
void SetEmpty() {
|
||||
index_data_16_.Clear();
|
||||
index_data_32_.Clear();
|
||||
}
|
||||
auto IsValid() const -> bool override {
|
||||
switch (index_data_size()) {
|
||||
case 4:
|
||||
return (index_data_32_.exists() && !index_data_32_->elements.empty());
|
||||
case 2:
|
||||
return (index_data_16_.exists() && !index_data_16_->elements.empty());
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Checks for valid index sizes given a data length.
|
||||
// Will print a one-time warning and return false if invalid.
|
||||
// For use by subclasses in their IsValid() overrides
|
||||
auto IndexSizeIsValid(size_t data_size) const -> bool {
|
||||
if (index_data_size() == 2 && data_size > 65535) {
|
||||
BA_LOG_ONCE("ERROR: got mesh data with > 65535 elems and 16 bit indices: "
|
||||
+ GetObjectDescription()
|
||||
+ ". This case requires 32 bit indices.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
auto GetIndexData() const -> MeshBufferBase* {
|
||||
switch (index_data_size()) {
|
||||
case 4:
|
||||
return index_data_32_.get();
|
||||
case 2:
|
||||
return index_data_16_.get();
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Object::Ref<MeshIndexBuffer32> index_data_32_;
|
||||
Object::Ref<MeshIndexBuffer16> index_data_16_;
|
||||
int index_data_size_ = 0;
|
||||
uint32_t index_state_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_BASE_H_
|
||||
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_DUAL_TEXTURE_FULL_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_DUAL_TEXTURE_FULL_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class MeshIndexedDualTextureFull
|
||||
: public MeshIndexed<VertexDualTextureFull,
|
||||
MeshDataType::kIndexedDualTextureFull> {
|
||||
using MeshIndexed::MeshIndexed; // wheee c++11 magic
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_DUAL_TEXTURE_FULL_H_
|
||||
20
src/ballistica/graphics/mesh/mesh_indexed_object_split.h
Normal file
20
src/ballistica/graphics/mesh/mesh_indexed_object_split.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_OBJECT_SPLIT_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_OBJECT_SPLIT_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_static_dynamic.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a mesh with static indices and UVs and dynamic positions and normals
|
||||
class MeshIndexedObjectSplit
|
||||
: public MeshIndexedStaticDynamic<VertexObjectSplitStatic,
|
||||
VertexObjectSplitDynamic,
|
||||
MeshDataType::kIndexedObjectSplit> {
|
||||
using MeshIndexedStaticDynamic::MeshIndexedStaticDynamic; // c++11 magic!
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_OBJECT_SPLIT_H_
|
||||
18
src/ballistica/graphics/mesh/mesh_indexed_simple_full.h
Normal file
18
src/ballistica/graphics/mesh/mesh_indexed_simple_full.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_FULL_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_FULL_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a simple mesh with all data provided together (either static or dynamic)
|
||||
class MeshIndexedSimpleFull
|
||||
: public MeshIndexed<VertexSimpleFull, MeshDataType::kIndexedSimpleFull> {
|
||||
using MeshIndexed::MeshIndexed; // wheee c++11 magic
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_FULL_H_
|
||||
20
src/ballistica/graphics/mesh/mesh_indexed_simple_split.h
Normal file
20
src/ballistica/graphics/mesh/mesh_indexed_simple_split.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_SPLIT_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_SPLIT_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_static_dynamic.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a mesh with static indices and UVs and dynamic positions
|
||||
class MeshIndexedSimpleSplit
|
||||
: public MeshIndexedStaticDynamic<VertexSimpleSplitStatic,
|
||||
VertexSimpleSplitDynamic,
|
||||
MeshDataType::kIndexedSimpleSplit> {
|
||||
using MeshIndexedStaticDynamic::MeshIndexedStaticDynamic; // c++11 magic!
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SIMPLE_SPLIT_H_
|
||||
18
src/ballistica/graphics/mesh/mesh_indexed_smoke_full.h
Normal file
18
src/ballistica/graphics/mesh/mesh_indexed_smoke_full.h
Normal file
@ -0,0 +1,18 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SMOKE_FULL_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SMOKE_FULL_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a mesh with all data provided together (either static or dynamic)
|
||||
class MeshIndexedSmokeFull
|
||||
: public MeshIndexed<VertexSmokeFull, MeshDataType::kIndexedSmokeFull> {
|
||||
using MeshIndexed::MeshIndexed; // wheee c++11 magic
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_SMOKE_FULL_H_
|
||||
58
src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h
Normal file
58
src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h
Normal file
@ -0,0 +1,58 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_STATIC_DYNAMIC_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_STATIC_DYNAMIC_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_base.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// mesh with static indices, some static vertex data,
|
||||
// and some dynamic vertex data
|
||||
template <typename STATICDATA, typename DYNAMICDATA, MeshDataType T>
|
||||
class MeshIndexedStaticDynamic : public MeshIndexedBase {
|
||||
public:
|
||||
MeshIndexedStaticDynamic() : MeshIndexedBase(T) {}
|
||||
void SetStaticData(const Object::Ref<MeshBuffer<STATICDATA>>& data) {
|
||||
assert(data->elements.size() > 0);
|
||||
static_data_ = data;
|
||||
static_data_->state = ++static_state_;
|
||||
}
|
||||
void SetDynamicData(const Object::Ref<MeshBuffer<DYNAMICDATA>>& data) {
|
||||
assert(data->elements.size() > 0);
|
||||
dynamic_data_ = data;
|
||||
dynamic_data_->state = ++dynamic_state_;
|
||||
}
|
||||
auto IsValid() const -> bool override {
|
||||
if (!static_data_.exists() || static_data_->elements.empty()
|
||||
|| !dynamic_data_.exists() || dynamic_data_->elements.empty()
|
||||
|| !MeshIndexedBase::IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Static and dynamic data sizes should always match, right?
|
||||
if (static_data_->elements.size() != dynamic_data_->elements.size()) {
|
||||
BA_LOG_ONCE("ERROR: mesh static and dynamic data sizes do not match");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure our index size covers our element count.
|
||||
return IndexSizeIsValid(static_data_->elements.size());
|
||||
}
|
||||
auto static_data() const -> const Object::Ref<MeshBuffer<STATICDATA>>& {
|
||||
return static_data_;
|
||||
}
|
||||
auto dynamic_data() const -> const Object::Ref<MeshBuffer<DYNAMICDATA>>& {
|
||||
return dynamic_data_;
|
||||
}
|
||||
|
||||
private:
|
||||
Object::Ref<MeshBuffer<STATICDATA>> static_data_;
|
||||
Object::Ref<MeshBuffer<DYNAMICDATA>> dynamic_data_;
|
||||
uint32_t static_state_{};
|
||||
uint32_t dynamic_state_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_INDEXED_STATIC_DYNAMIC_H_
|
||||
42
src/ballistica/graphics/mesh/mesh_non_indexed.h
Normal file
42
src/ballistica/graphics/mesh/mesh_non_indexed.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_NON_INDEXED_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_NON_INDEXED_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Mesh using non-indexed vertex data. Good for situations where vertices
|
||||
// are never shared between primitives (such as drawing points/sprites/etc).
|
||||
template <typename DATA, MeshDataType T>
|
||||
class MeshNonIndexed : public Mesh {
|
||||
public:
|
||||
explicit MeshNonIndexed(MeshDrawType drawType = MeshDrawType::kDynamic)
|
||||
: Mesh(T, drawType), data_state_(0) {}
|
||||
// NOLINTNEXTLINE
|
||||
void SetData(const Object::Ref<MeshBuffer<DATA>>& data) {
|
||||
data_ = data;
|
||||
data_->state = ++data_state_;
|
||||
}
|
||||
|
||||
// Call this if you have nothing to draw.
|
||||
void SetEmpty() { data_.clear(); }
|
||||
auto IsValid() const -> bool override {
|
||||
#if BA_DEBUG_BUILD
|
||||
// Make extra sure that we're actually valid in debug mode.
|
||||
if (data_.exists()) {
|
||||
assert(data_->elements.size() > 0);
|
||||
}
|
||||
#endif
|
||||
return (data_.exists());
|
||||
}
|
||||
|
||||
private:
|
||||
Object::Ref<MeshBuffer<DATA>> data_{};
|
||||
uint32_t data_state_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_NON_INDEXED_H_
|
||||
15
src/ballistica/graphics/mesh/mesh_renderer_data.h
Normal file
15
src/ballistica/graphics/mesh/mesh_renderer_data.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_MESH_RENDERER_DATA_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_MESH_RENDERER_DATA_H_
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class MeshRendererData {
|
||||
public:
|
||||
virtual ~MeshRendererData() = default;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_MESH_RENDERER_DATA_H_
|
||||
17
src/ballistica/graphics/mesh/sprite_mesh.h
Normal file
17
src/ballistica/graphics/mesh/sprite_mesh.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_SPRITE_MESH_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_SPRITE_MESH_H_
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// an indexed sprite-mesh
|
||||
class SpriteMesh : public MeshIndexed<VertexSprite, MeshDataType::kSprite> {
|
||||
using MeshIndexed::MeshIndexed; // wheeee c++11 magic
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_SPRITE_MESH_H_
|
||||
574
src/ballistica/graphics/mesh/text_mesh.cc
Normal file
574
src/ballistica/graphics/mesh/text_mesh.cc
Normal file
@ -0,0 +1,574 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/mesh/text_mesh.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/graphics/text/text_graphics.h"
|
||||
#include "ballistica/graphics/text/text_packer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
TextMesh::TextMesh() : MeshIndexedDualTextureFull(MeshDrawType::kStatic) {}
|
||||
|
||||
void TextMesh::SetText(const std::string& text_in, HAlign alignment_h,
|
||||
VAlign alignment_v, bool big, uint32_t min_val,
|
||||
uint32_t max_val, TextMeshEntryType entry_type,
|
||||
TextPacker* packer) {
|
||||
if (text_in == text_) {
|
||||
// Covers corner case where we assign a new string to empty.
|
||||
if (text_in.empty()) {
|
||||
SetEmpty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
text_ = text_in;
|
||||
|
||||
assert(Utils::IsValidUTF8(text_));
|
||||
|
||||
const char* txt = text_in.c_str();
|
||||
|
||||
// Quick-out for empty strings.
|
||||
if (txt[0] == 0) {
|
||||
SetEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry_type == TextMeshEntryType::kOSRendered) {
|
||||
assert(packer != nullptr);
|
||||
}
|
||||
|
||||
// Start buffers big enough to handle the worst case
|
||||
// (every char being a discrete letter).
|
||||
int text_size = static_cast<int>(text_in.size());
|
||||
assert(text_size > 0);
|
||||
Object::Ref<MeshIndexBuffer16> indices16;
|
||||
Object::Ref<MeshIndexBuffer32> indices32;
|
||||
|
||||
// Go with 32 bit indices if there's any chance we'll have over 65535 pts;
|
||||
// otherwise go with 16 bit.
|
||||
// NOTE: disabling 32 bit indices for now; turns out they're
|
||||
// not supported in OpenGL ES2 :-(
|
||||
// It may be worth adding logic to split up meshes into multiple
|
||||
// draw-calls. (or we can just wait until ES2 is dead).
|
||||
if (explicit_bool(false) && 4 * text_size > 65535) {
|
||||
indices32 = Object::New<MeshIndexBuffer32>(6 * (text_size));
|
||||
} else {
|
||||
indices16 = Object::New<MeshIndexBuffer16>(6 * (text_size));
|
||||
}
|
||||
auto vertices(Object::New<MeshBuffer<VertexDualTextureFull>>(4 * text_size));
|
||||
|
||||
uint16_t* index16 = indices16.exists() ? indices16->elements.data() : nullptr;
|
||||
uint32_t* index32 = indices32.exists() ? indices32->elements.data() : nullptr;
|
||||
|
||||
VertexDualTextureFull* v = &vertices->elements[0];
|
||||
uint32_t index_offset = 0;
|
||||
float x = 0;
|
||||
float x_offset, y_offset;
|
||||
x_offset = x;
|
||||
|
||||
float char_width;
|
||||
float char_height;
|
||||
float row_height;
|
||||
float char_offset_h;
|
||||
float char_offset_v;
|
||||
|
||||
char_width = char_height = 32.0f;
|
||||
row_height = kTextRowHeight;
|
||||
char_offset_h = -3.0f;
|
||||
char_offset_v = 7.0f;
|
||||
uint32_t char_val;
|
||||
float line_length;
|
||||
|
||||
float l = 0;
|
||||
float r = 0;
|
||||
float b = 0;
|
||||
float t = 0;
|
||||
|
||||
float text_height;
|
||||
|
||||
// Pre-calc the height of the text (if needed).
|
||||
switch (alignment_v) {
|
||||
case VAlign::kNone:
|
||||
case VAlign::kTop:
|
||||
text_height = 0; // Not used here.
|
||||
break;
|
||||
case VAlign::kCenter:
|
||||
case VAlign::kBottom: {
|
||||
int rows = 1;
|
||||
for (const char* c = txt; *c != 0; c++) {
|
||||
if (*c == '\n') rows++;
|
||||
}
|
||||
text_height = static_cast<float>(rows) * row_height;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
|
||||
switch (alignment_v) {
|
||||
case VAlign::kNone:
|
||||
y_offset = b + char_offset_v;
|
||||
break;
|
||||
case VAlign::kTop:
|
||||
y_offset = b + char_offset_v + (t - b) - row_height;
|
||||
break;
|
||||
case VAlign::kCenter:
|
||||
y_offset =
|
||||
b + char_offset_v + ((t - b) / 2) + (text_height / 2) - row_height;
|
||||
break;
|
||||
case VAlign::kBottom:
|
||||
y_offset = b + char_offset_v + text_height - row_height;
|
||||
break;
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
|
||||
const char* tc = txt;
|
||||
bool first_char = true;
|
||||
|
||||
std::vector<uint32_t> os_span;
|
||||
|
||||
while (*tc != 0) {
|
||||
const char* tc_prev = tc;
|
||||
|
||||
char_val = Utils::GetUTF8Value(tc);
|
||||
|
||||
Utils::AdvanceUTF8(&tc);
|
||||
|
||||
// Reset alignment on new lines.
|
||||
if (first_char || char_val == '\n') {
|
||||
// If we've been building an os-span, add it to the text-packer.
|
||||
if (char_val == '\n' && !os_span.empty()) {
|
||||
Rect r2;
|
||||
float width;
|
||||
std::string s = Utils::UTF8FromUnicode(os_span);
|
||||
g_text_graphics->GetOSTextSpanBoundsAndWidth(s, &r2, &width);
|
||||
if (packer) {
|
||||
packer->AddSpan(s, x_offset, y_offset, r2);
|
||||
}
|
||||
os_span.clear();
|
||||
}
|
||||
|
||||
switch (alignment_h) {
|
||||
case HAlign::kLeft:
|
||||
x_offset = l + char_offset_h;
|
||||
break;
|
||||
case HAlign::kCenter:
|
||||
case HAlign::kRight: {
|
||||
// For some alignments we need to pre-calc the length of the line.
|
||||
line_length = 0;
|
||||
const char* c;
|
||||
|
||||
// If this was the first char, include it in this line tally.
|
||||
// if it was a newline, don't.
|
||||
if (first_char) {
|
||||
c = tc_prev;
|
||||
} else {
|
||||
c = tc;
|
||||
}
|
||||
|
||||
// We have the OS render some chars, broken into single-line spans.
|
||||
std::vector<uint32_t> os_span_l;
|
||||
|
||||
while (true) {
|
||||
uint32_t val;
|
||||
if (*c == 0) { // NOLINT(bugprone-branch-clone)
|
||||
break;
|
||||
} else if (*c == '\n') {
|
||||
break;
|
||||
} else {
|
||||
val = Utils::GetUTF8Value(c);
|
||||
Utils::AdvanceUTF8(&c);
|
||||
|
||||
// Special case: if we're already doing an OS-span, tack certain
|
||||
// chars onto it instead of switching back to glyph mode.
|
||||
// (to reduce the number of times we switch back and forth)
|
||||
if (TextGraphics::IsOSDrawableAscii(val) && !os_span_l.empty()) {
|
||||
os_span_l.push_back(val);
|
||||
} else if (TextGraphics::Glyph* g =
|
||||
g_text_graphics->GetGlyph(val, big)) {
|
||||
// Flipping back to glyphs; if we had been building an os_span,
|
||||
// tally it.
|
||||
if (!os_span_l.empty()) {
|
||||
std::string s = Utils::UTF8FromUnicode(os_span_l);
|
||||
line_length += g_text_graphics->GetOSTextSpanWidth(s);
|
||||
os_span_l.clear();
|
||||
}
|
||||
line_length += char_width * g->advance;
|
||||
} else {
|
||||
// Not a glyph char: add it to our current span to handle
|
||||
// through the OS.
|
||||
|
||||
if (g_buildconfig.enable_os_font_rendering()) {
|
||||
os_span_l.push_back(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add final os_span if there is one.
|
||||
if (!os_span_l.empty()) {
|
||||
std::string s = Utils::UTF8FromUnicode(os_span_l);
|
||||
line_length += g_text_graphics->GetOSTextSpanWidth(s);
|
||||
os_span_l.clear();
|
||||
}
|
||||
if (alignment_h == HAlign::kCenter) {
|
||||
x_offset = l + char_offset_h + ((r - l) / 2) - (line_length / 2);
|
||||
} else {
|
||||
x_offset = l + char_offset_h + (r - l) - line_length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
first_char = false;
|
||||
}
|
||||
|
||||
switch (char_val) { // NOLINT
|
||||
case '\n':
|
||||
y_offset -= row_height;
|
||||
break;
|
||||
|
||||
default: {
|
||||
bool draw = true;
|
||||
if (char_val < min_val || char_val > max_val) draw = false;
|
||||
|
||||
// Only draw the private-use range when doing our extras sheets.
|
||||
// (technically OS might be able to render these but don't allow that)
|
||||
if (entry_type != TextMeshEntryType::kExtras
|
||||
&& (char_val >= 0xE000 && char_val <= 0xF8FF)) {
|
||||
draw = false;
|
||||
}
|
||||
|
||||
// Special case: if we're already doing an OS-span, tack certain
|
||||
// chars onto it instead of switching back to glyph mode.
|
||||
// (to reduce the number of times we switch back and forth)
|
||||
if (TextGraphics::IsOSDrawableAscii(char_val) && !os_span.empty()) {
|
||||
os_span.push_back(char_val);
|
||||
} else if (TextGraphics::Glyph* glyph =
|
||||
g_text_graphics->GetGlyph(char_val, big)) {
|
||||
// If we had been building up an OS-text span,
|
||||
// commit it since we're flipping to glyphs now.
|
||||
if (!os_span.empty()) {
|
||||
Rect r2;
|
||||
float width;
|
||||
std::string s = Utils::UTF8FromUnicode(os_span);
|
||||
g_text_graphics->GetOSTextSpanBoundsAndWidth(s, &r2, &width);
|
||||
if (packer) packer->AddSpan(s, x_offset, y_offset, r2);
|
||||
x_offset += width;
|
||||
os_span.clear();
|
||||
}
|
||||
|
||||
// Draw this glyph.
|
||||
if (draw) {
|
||||
float v_min = glyph->tex_min_y;
|
||||
float v_max = glyph->tex_max_y;
|
||||
float u_min = glyph->tex_min_x;
|
||||
float u_max = glyph->tex_max_x;
|
||||
auto v_max_i = static_cast<int>(65535.0f * v_max);
|
||||
auto v_min_i = static_cast<int>(65535.0f * v_min);
|
||||
auto u_max_i = static_cast<int>(65535.0f * u_max);
|
||||
auto u_min_i = static_cast<int>(65535.0f * u_min);
|
||||
|
||||
if (index16) {
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 0);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 3);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
}
|
||||
if (index32) {
|
||||
*index32++ = index_offset + 0;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 2;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 3;
|
||||
*index32++ = index_offset + 2;
|
||||
}
|
||||
|
||||
// Bot left.
|
||||
v->position[0] = x_offset + char_width * glyph->pen_offset_x;
|
||||
v->position[1] = y_offset + char_height * glyph->pen_offset_y;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Bot right.
|
||||
v->position[0] =
|
||||
x_offset + char_width * (glyph->pen_offset_x + glyph->x_size);
|
||||
v->position[1] = y_offset + char_height * glyph->pen_offset_y;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Top left.
|
||||
v->position[0] = x_offset + char_width * (glyph->pen_offset_x);
|
||||
v->position[1] =
|
||||
y_offset + char_height * (glyph->pen_offset_y + glyph->y_size);
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
|
||||
// Top right.
|
||||
v->position[0] =
|
||||
x_offset + char_width * (glyph->pen_offset_x + glyph->x_size);
|
||||
v->position[1] =
|
||||
y_offset + char_height * (glyph->pen_offset_y + glyph->y_size);
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
index_offset += 4;
|
||||
}
|
||||
x_offset += char_width * glyph->advance;
|
||||
} else {
|
||||
// Add to the single-line span we'll ask the OS to render.
|
||||
if (g_buildconfig.enable_os_font_rendering()) {
|
||||
os_span.push_back(char_val);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit any final OS-text span (can skip this if we're not
|
||||
// the one drawing OS text).
|
||||
if ((!os_span.empty()) && packer) {
|
||||
Rect r2;
|
||||
float width;
|
||||
std::string s = Utils::UTF8FromUnicode(os_span);
|
||||
g_text_graphics->GetOSTextSpanBoundsAndWidth(s, &r2, &width);
|
||||
packer->AddSpan(s, x_offset, y_offset, r2);
|
||||
os_span.clear();
|
||||
}
|
||||
|
||||
// Now if we've been building a text-packer,
|
||||
// compile it and add its final spans to our mesh.
|
||||
if (packer) {
|
||||
std::vector<TextPacker::Span> spans;
|
||||
packer->compile();
|
||||
|
||||
// DEBUGGING - add a single quad above our first
|
||||
// span showing the entire texture for debugging purposes
|
||||
if (explicit_bool(false) && !packer->spans().empty()) {
|
||||
int v_max_i = static_cast<int>(65535 * 1.0f);
|
||||
int v_min_i = static_cast<int>(65535 * 0.0f);
|
||||
int u_max_i = static_cast<int>(65535 * 1.0f);
|
||||
int u_min_i = static_cast<int>(65535 * 0.0f);
|
||||
|
||||
if (index16) {
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 0);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 3);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
}
|
||||
if (index32) {
|
||||
*index32++ = index_offset + 0;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 2;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 3;
|
||||
*index32++ = index_offset + 2;
|
||||
}
|
||||
|
||||
x_offset =
|
||||
packer->spans().front().bounds.l + packer->spans().front().x - 80.0f;
|
||||
y_offset =
|
||||
packer->spans().front().bounds.t + packer->spans().front().y + 90.0f;
|
||||
|
||||
float width = static_cast<float>(packer->texture_width()) * 0.7f;
|
||||
float height = static_cast<float>(packer->texture_height()) * 0.7f;
|
||||
|
||||
// Bottom left.
|
||||
v->position[0] = x_offset;
|
||||
v->position[1] = y_offset;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Bottom right.
|
||||
v->position[0] = x_offset + width;
|
||||
v->position[1] = y_offset;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Top left.
|
||||
v->position[0] = x_offset;
|
||||
v->position[1] = y_offset + height;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
|
||||
// Top right.
|
||||
v->position[0] = x_offset + width;
|
||||
v->position[1] = y_offset + height;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
|
||||
index_offset += 4;
|
||||
}
|
||||
|
||||
for (auto&& i : packer->spans()) {
|
||||
int v_max_i =
|
||||
std::max(0, std::min(65535, static_cast<int>(65535 * i.v_max)));
|
||||
int v_min_i =
|
||||
std::max(0, std::min(65535, static_cast<int>(65535 * i.v_min)));
|
||||
int u_max_i =
|
||||
std::max(0, std::min(65535, static_cast<int>(65535 * i.u_max)));
|
||||
int u_min_i =
|
||||
std::max(0, std::min(65535, static_cast<int>(65535 * i.u_min)));
|
||||
|
||||
if (index16) {
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 0);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 1);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 3);
|
||||
*index16++ = static_cast_check_fit<uint16_t>(index_offset + 2);
|
||||
}
|
||||
if (index32) {
|
||||
*index32++ = index_offset + 0;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 2;
|
||||
*index32++ = index_offset + 1;
|
||||
*index32++ = index_offset + 3;
|
||||
*index32++ = index_offset + 2;
|
||||
}
|
||||
|
||||
// Fudge-factor for lining OS-spans up with our stuff.
|
||||
x_offset = i.x + 3.0f;
|
||||
y_offset = i.y;
|
||||
|
||||
// Bot left.
|
||||
v->position[0] = x_offset + i.draw_bounds.l;
|
||||
v->position[1] = y_offset + i.draw_bounds.b;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Bot right.
|
||||
v->position[0] = x_offset + i.draw_bounds.r;
|
||||
v->position[1] = y_offset + i.draw_bounds.b;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_max_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 65535;
|
||||
v++;
|
||||
|
||||
// Top left.
|
||||
v->position[0] = x_offset + i.draw_bounds.l;
|
||||
v->position[1] = y_offset + i.draw_bounds.t;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_min_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 0;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
|
||||
// Top right.
|
||||
v->position[0] = x_offset + i.draw_bounds.r;
|
||||
v->position[1] = y_offset + i.draw_bounds.t;
|
||||
v->position[2] = 0;
|
||||
v->uv[0] = static_cast_check_fit<uint16_t>(u_max_i);
|
||||
v->uv[1] = static_cast_check_fit<uint16_t>(v_min_i);
|
||||
v->uv2[0] = 65535;
|
||||
v->uv2[1] = 0;
|
||||
v++;
|
||||
|
||||
index_offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we didn't overshoot the end of our buffer.
|
||||
if (index16) {
|
||||
assert((index16 - indices16->elements.data())
|
||||
<= static_cast<int>(indices16->elements.size()));
|
||||
}
|
||||
if (index32) {
|
||||
assert((index32 - indices32->elements.data())
|
||||
<= static_cast<int>(indices32->elements.size()));
|
||||
}
|
||||
assert((v - (&(vertices->elements[0])))
|
||||
<= static_cast<int>(vertices->elements.size()));
|
||||
|
||||
// clamp to what we actually used..
|
||||
if (index16) {
|
||||
indices16->elements.resize(index16 - (indices16->elements.data()));
|
||||
}
|
||||
if (index32) {
|
||||
indices32->elements.resize(index32 - (indices32->elements.data()));
|
||||
}
|
||||
vertices->elements.resize(v - (&(vertices->elements[0])));
|
||||
|
||||
// Either set data or abort if empty.
|
||||
if (index16 && !indices16->elements.empty()) {
|
||||
SetIndexData(indices16);
|
||||
SetData(vertices);
|
||||
} else if (index32 && !indices32->elements.empty()) {
|
||||
// In a lot of cases we actually wind up with fewer than 65535 pts.
|
||||
// (we theoretically could have needed more which is why we went 32bit).
|
||||
// ...lets go ahead and downsize to 16 bit in this case to save a wee bit
|
||||
// of gpu memory.
|
||||
if (vertices->elements.size() < 65535) {
|
||||
int size = static_cast<int>(indices32->elements.size());
|
||||
indices16 = Object::NewDeferred<MeshIndexBuffer16>(size);
|
||||
uint16_t* i16 = indices16->elements.data();
|
||||
uint32_t* i32 = indices32->elements.data();
|
||||
for (int i = 0; i < size; i++) {
|
||||
*i16++ = static_cast_check_fit<uint16_t>(*i32++);
|
||||
}
|
||||
assert((i32 - indices32->elements.data())
|
||||
<= static_cast<int>(indices32->elements.size()));
|
||||
assert((i16 - indices16->elements.data())
|
||||
<= static_cast<int>(indices16->elements.size()));
|
||||
SetIndexData(indices16);
|
||||
} else {
|
||||
// we *actually* need 32 bit indices...
|
||||
SetIndexData(indices32);
|
||||
}
|
||||
SetData(vertices);
|
||||
} else {
|
||||
SetEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
32
src/ballistica/graphics/mesh/text_mesh.h
Normal file
32
src/ballistica/graphics/mesh/text_mesh.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_MESH_TEXT_MESH_H_
|
||||
#define BALLISTICA_GRAPHICS_MESH_TEXT_MESH_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/graphics/mesh/mesh_indexed_dual_texture_full.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// a mesh set up to draw text
|
||||
// in general you should not use this directly; use TextGroup below, which will
|
||||
// automatically handle switching meshes/textures in order to support the full
|
||||
// unicode range
|
||||
class TextMesh : public MeshIndexedDualTextureFull {
|
||||
public:
|
||||
enum class HAlign { kLeft, kCenter, kRight };
|
||||
enum class VAlign { kNone, kBottom, kCenter, kTop };
|
||||
TextMesh();
|
||||
void SetText(const std::string& text, HAlign alignment_h, VAlign alignment_v,
|
||||
bool big, uint32_t min_val, uint32_t max_val,
|
||||
TextMeshEntryType entry_type, TextPacker* packer);
|
||||
auto text() const -> const std::string& { return text_; }
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_MESH_TEXT_MESH_H_
|
||||
153
src/ballistica/graphics/net_graph.cc
Normal file
153
src/ballistica/graphics/net_graph.cc
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/net_graph.h"
|
||||
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include "ballistica/graphics/component/simple_component.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class NetGraph::Impl {
|
||||
public:
|
||||
std::list<std::pair<double, float>> samples;
|
||||
double duration = 2000.0;
|
||||
double v_max_smoothed = 1.0;
|
||||
ImageMesh bg_mesh;
|
||||
MeshIndexedSimpleFull value_mesh;
|
||||
TextGroup max_vel_text;
|
||||
};
|
||||
|
||||
NetGraph::NetGraph() : impl_(new NetGraph::Impl()) {}
|
||||
|
||||
NetGraph::~NetGraph() = default;
|
||||
|
||||
void NetGraph::AddSample(double time, double value) {
|
||||
impl_->samples.emplace_back(time, value);
|
||||
double cutoffTime = time - impl_->duration;
|
||||
|
||||
// Go ahead and prune old ones here so we don't grow out of control.
|
||||
std::list<std::pair<double, float>>::iterator i;
|
||||
for (i = impl_->samples.begin(); i != impl_->samples.end();) {
|
||||
if (i->first < cutoffTime) {
|
||||
auto i_next = i;
|
||||
++i_next;
|
||||
impl_->samples.erase(i);
|
||||
i = i_next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetGraph::Draw(RenderPass* pass, double time, double x, double y, double w,
|
||||
double h) {
|
||||
impl_->bg_mesh.SetPositionAndSize(
|
||||
static_cast<float>(x), static_cast<float>(y), 0.0f, static_cast<float>(w),
|
||||
static_cast<float>(h));
|
||||
|
||||
int num_samples = static_cast<int>(impl_->samples.size());
|
||||
|
||||
// Draw values (provided we have at least 2 samples)
|
||||
bool draw_values = (num_samples >= 2);
|
||||
if (draw_values) {
|
||||
double t_left = time - impl_->duration;
|
||||
double t_right = time;
|
||||
double t_width = t_right - t_left;
|
||||
double v_bottom = 0.0f;
|
||||
|
||||
// Find the max y value we have and smoothly transition our bounds towards
|
||||
// that.
|
||||
double v_max = 0.0;
|
||||
for (auto&& s : impl_->samples) {
|
||||
if (s.second > v_max) {
|
||||
v_max = s.second;
|
||||
}
|
||||
}
|
||||
double smoothing = 0.95;
|
||||
impl_->v_max_smoothed =
|
||||
smoothing * impl_->v_max_smoothed + (1.0 - smoothing) * v_max * 1.1;
|
||||
|
||||
double v_top = impl_->v_max_smoothed;
|
||||
double v_height = v_top - v_bottom;
|
||||
|
||||
// We need 2 verts per sample.
|
||||
auto vertex_buffer(
|
||||
Object::New<MeshBuffer<VertexSimpleFull>>(num_samples * 2));
|
||||
VertexSimpleFull* v = vertex_buffer->elements.data();
|
||||
for (auto&& s : impl_->samples) {
|
||||
double t = s.first;
|
||||
double val = s.second;
|
||||
double vx = x + w * ((t - t_left) / t_width);
|
||||
double vy = y + h * ((val - v_bottom) / v_height);
|
||||
v->position[0] = static_cast<float>(vx);
|
||||
v->position[1] = static_cast<float>(y);
|
||||
v->position[2] = 0.0f;
|
||||
v->uv[0] = v->uv[1] = 0;
|
||||
v++;
|
||||
v->position[0] = static_cast<float>(vx);
|
||||
v->position[1] = static_cast<float>(vy);
|
||||
v->position[2] = 0.0f;
|
||||
v->uv[0] = v->uv[1] = 0;
|
||||
v++;
|
||||
}
|
||||
|
||||
// We need 2 tris per sample (minus the last).
|
||||
auto index_buffer(Object::New<MeshIndexBuffer16>((num_samples - 1) * 6));
|
||||
uint16_t* i = index_buffer->elements.data();
|
||||
auto s = impl_->samples.begin();
|
||||
int v_count = 0;
|
||||
while (true) {
|
||||
auto s_next = s;
|
||||
++s_next;
|
||||
if (s_next == impl_->samples.end()) {
|
||||
break;
|
||||
} else {
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count);
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count + 2);
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count + 1);
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count + 2);
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count + 3);
|
||||
*i++ = static_cast_check_fit<uint16_t>(v_count + 1);
|
||||
}
|
||||
v_count += 2;
|
||||
s = s_next;
|
||||
}
|
||||
impl_->value_mesh.SetIndexData(index_buffer);
|
||||
impl_->value_mesh.SetData(vertex_buffer);
|
||||
}
|
||||
|
||||
SimpleComponent c(pass);
|
||||
c.SetTransparent(true);
|
||||
c.SetColor(0.35f, 0.0f, 0.0f, 0.9f);
|
||||
c.DrawMesh(&impl_->bg_mesh);
|
||||
c.SetColor(0.0f, 1.0f, 0.0f, 0.85f);
|
||||
if (draw_values) {
|
||||
c.DrawMesh(&impl_->value_mesh);
|
||||
}
|
||||
c.Submit();
|
||||
|
||||
char val_str[32];
|
||||
snprintf(val_str, sizeof(val_str), "%.2f", impl_->v_max_smoothed);
|
||||
impl_->max_vel_text.SetText(val_str, TextMesh::HAlign::kLeft,
|
||||
TextMesh::VAlign::kTop);
|
||||
|
||||
SimpleComponent c2(pass);
|
||||
c2.SetTransparent(true);
|
||||
c2.SetColor(1, 0, 0, 1);
|
||||
c2.PushTransform();
|
||||
c2.Translate(static_cast<float>(x), static_cast<float>(y + h));
|
||||
float scale = static_cast<float>(h) * 0.006f;
|
||||
c2.Scale(scale, scale);
|
||||
int text_elem_count = impl_->max_vel_text.GetElementCount();
|
||||
for (int e = 0; e < text_elem_count; e++) {
|
||||
c2.SetTexture(impl_->max_vel_text.GetElementTexture(e));
|
||||
c2.SetFlatness(1.0f);
|
||||
c2.DrawMesh(impl_->max_vel_text.GetElementMesh(e));
|
||||
}
|
||||
c2.PopTransform();
|
||||
c2.Submit();
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
27
src/ballistica/graphics/net_graph.h
Normal file
27
src/ballistica/graphics/net_graph.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_NET_GRAPH_H_
|
||||
#define BALLISTICA_GRAPHICS_NET_GRAPH_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class NetGraph : public Object {
|
||||
public:
|
||||
NetGraph();
|
||||
~NetGraph() override;
|
||||
void AddSample(double time, double value);
|
||||
void Draw(RenderPass* pass, double time, double x, double y, double w,
|
||||
double h);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_NET_GRAPH_H_
|
||||
576
src/ballistica/graphics/render_command_buffer.h
Normal file
576
src/ballistica/graphics/render_command_buffer.h
Normal file
@ -0,0 +1,576 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_RENDER_COMMAND_BUFFER_H_
|
||||
#define BALLISTICA_GRAPHICS_RENDER_COMMAND_BUFFER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/graphics/frame_def.h"
|
||||
#include "ballistica/graphics/mesh/mesh.h"
|
||||
#include "ballistica/math/matrix44f.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class RenderCommandBuffer {
|
||||
public:
|
||||
// IMPORTANT: make sure to update has_draw_commands with any new
|
||||
// ones added here.
|
||||
enum class Command {
|
||||
kEnd,
|
||||
kShader,
|
||||
kDrawModel,
|
||||
kDrawModelInstanced,
|
||||
kDrawMesh,
|
||||
kDrawScreenQuad,
|
||||
kScissorPush,
|
||||
kScissorPop,
|
||||
kPushTransform,
|
||||
kPopTransform,
|
||||
kTranslate2,
|
||||
kTranslate3,
|
||||
kCursorTranslate,
|
||||
kScaleUniform,
|
||||
kTranslateToProjectedPoint,
|
||||
#if BA_VR_BUILD
|
||||
kTransformToRightHand,
|
||||
kTransformToLeftHand,
|
||||
kTransformToHead,
|
||||
#endif
|
||||
kScale2,
|
||||
kScale3,
|
||||
kRotate,
|
||||
kMultMatrix,
|
||||
kFlipCullFace,
|
||||
kSimpleComponentInlineColor,
|
||||
kObjectComponentInlineColor,
|
||||
kObjectComponentInlineAddColor,
|
||||
kBeginDebugDrawTriangles,
|
||||
kBeginDebugDrawLines,
|
||||
kEndDebugDraw,
|
||||
kDebugDrawVertex3
|
||||
};
|
||||
|
||||
RenderCommandBuffer() = default;
|
||||
void PutCommand(Command c) {
|
||||
assert(!finalized_);
|
||||
commands_.push_back(c);
|
||||
}
|
||||
|
||||
void PutFloat(float val) {
|
||||
assert(!finalized_);
|
||||
fvals_.push_back(val);
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 2);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f = f2;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 3);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f = f3;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 4);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f = f4;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 5);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f = f5;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 6);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f = f6;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 7);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f = f7;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7, float f8) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 8);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f++ = f7;
|
||||
*f = f8;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7, float f8, float f9) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 9);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f++ = f7;
|
||||
*f++ = f8;
|
||||
*f = f9;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7, float f8, float f9, float f10) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 10);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f++ = f7;
|
||||
*f++ = f8;
|
||||
*f++ = f9;
|
||||
*f = f10;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7, float f8, float f9, float f10, float f11,
|
||||
float f12) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 12);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f++ = f7;
|
||||
*f++ = f8;
|
||||
*f++ = f9;
|
||||
*f++ = f10;
|
||||
*f++ = f11;
|
||||
*f = f12;
|
||||
}
|
||||
|
||||
void PutFloats(float f1, float f2, float f3, float f4, float f5, float f6,
|
||||
float f7, float f8, float f9, float f10, float f11, float f12,
|
||||
float f13, float f14, float f15) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 15);
|
||||
float* f = &(fvals_[s]);
|
||||
*f++ = f1;
|
||||
*f++ = f2;
|
||||
*f++ = f3;
|
||||
*f++ = f4;
|
||||
*f++ = f5;
|
||||
*f++ = f6;
|
||||
*f++ = f7;
|
||||
*f++ = f8;
|
||||
*f++ = f9;
|
||||
*f++ = f10;
|
||||
*f++ = f11;
|
||||
*f++ = f12;
|
||||
*f++ = f13;
|
||||
*f++ = f14;
|
||||
*f = f15;
|
||||
}
|
||||
|
||||
void PutFloatArray16(const float* f_in) {
|
||||
assert(!finalized_);
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
fvals_.resize(fvals_.size() + 16);
|
||||
float* f = &(fvals_[s]);
|
||||
memcpy(f, f_in, sizeof(float) * 16);
|
||||
}
|
||||
|
||||
void PutMatrices(const std::vector<Matrix44f>& mats) {
|
||||
assert(!finalized_);
|
||||
static_assert(sizeof(Matrix44f[2]) == sizeof(float[16][2]));
|
||||
ivals_.push_back(static_cast<int>(mats.size()));
|
||||
int s = static_cast<int>(fvals_.size());
|
||||
int f_count = static_cast<int>(16 * mats.size());
|
||||
fvals_.resize(fvals_.size() + f_count);
|
||||
float* f = &(fvals_[s]);
|
||||
const float* f_in = mats[0].m;
|
||||
memcpy(f, f_in, sizeof(float) * f_count);
|
||||
}
|
||||
|
||||
void PutInt(int val) {
|
||||
assert(!finalized_);
|
||||
ivals_.push_back(val);
|
||||
}
|
||||
|
||||
void PutModel(ModelData* model) {
|
||||
assert(frame_def_);
|
||||
assert(!finalized_);
|
||||
frame_def_->AddComponent(Object::Ref<MediaComponentData>(model));
|
||||
models_.push_back(model);
|
||||
}
|
||||
|
||||
void PutTexture(TextureData* texture) {
|
||||
assert(frame_def_);
|
||||
assert(!finalized_);
|
||||
frame_def_->AddComponent(Object::Ref<MediaComponentData>(texture));
|
||||
textures_.push_back(texture);
|
||||
}
|
||||
|
||||
void PutTexture(const Object::Ref<TextureData>& texture) {
|
||||
assert(texture.exists());
|
||||
PutTexture(texture.get());
|
||||
}
|
||||
|
||||
void PutCubeMapTexture(TextureData* texture) {
|
||||
assert(frame_def_);
|
||||
assert(!finalized_);
|
||||
frame_def_->AddComponent(Object::Ref<MediaComponentData>(texture));
|
||||
textures_.push_back(texture);
|
||||
}
|
||||
|
||||
void PutMeshData(MeshData* mesh_data) {
|
||||
assert(!finalized_);
|
||||
mesh_datas_.push_back(mesh_data);
|
||||
}
|
||||
|
||||
// Return next item.
|
||||
auto GetCommand() -> Command {
|
||||
assert(finalized_);
|
||||
assert(commands_index_ <= commands_.size());
|
||||
return (commands_index_ == commands_.size()) ? Command::kEnd
|
||||
: commands_[commands_index_++];
|
||||
}
|
||||
|
||||
auto GetInt() -> int {
|
||||
assert(finalized_);
|
||||
assert(ivals_index_ < ivals_.size());
|
||||
return ivals_[ivals_index_++];
|
||||
}
|
||||
|
||||
auto GetFloat() -> float {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ < fvals_.size());
|
||||
return fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 2 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 3 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 4 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 5 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 6 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 7 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7, float* f8) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 8 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
*f8 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7, float* f8, float* f9) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 9 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
*f8 = fvals_[fvals_index_++];
|
||||
*f9 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7, float* f8, float* f9, float* f10) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 10 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
*f8 = fvals_[fvals_index_++];
|
||||
*f9 = fvals_[fvals_index_++];
|
||||
*f10 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7, float* f8, float* f9, float* f10,
|
||||
float* f11, float* f12) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 12 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
*f8 = fvals_[fvals_index_++];
|
||||
*f9 = fvals_[fvals_index_++];
|
||||
*f10 = fvals_[fvals_index_++];
|
||||
*f11 = fvals_[fvals_index_++];
|
||||
*f12 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
void GetFloats(float* f1, float* f2, float* f3, float* f4, float* f5,
|
||||
float* f6, float* f7, float* f8, float* f9, float* f10,
|
||||
float* f11, float* f12, float* f13, float* f14, float* f15) {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 15 <= fvals_.size());
|
||||
*f1 = fvals_[fvals_index_++];
|
||||
*f2 = fvals_[fvals_index_++];
|
||||
*f3 = fvals_[fvals_index_++];
|
||||
*f4 = fvals_[fvals_index_++];
|
||||
*f5 = fvals_[fvals_index_++];
|
||||
*f6 = fvals_[fvals_index_++];
|
||||
*f7 = fvals_[fvals_index_++];
|
||||
*f8 = fvals_[fvals_index_++];
|
||||
*f9 = fvals_[fvals_index_++];
|
||||
*f10 = fvals_[fvals_index_++];
|
||||
*f11 = fvals_[fvals_index_++];
|
||||
*f12 = fvals_[fvals_index_++];
|
||||
*f13 = fvals_[fvals_index_++];
|
||||
*f14 = fvals_[fvals_index_++];
|
||||
*f15 = fvals_[fvals_index_++];
|
||||
}
|
||||
|
||||
auto GetMatrix() -> Matrix44f* {
|
||||
assert(finalized_);
|
||||
assert(fvals_index_ + 16 <= fvals_.size());
|
||||
static_assert(sizeof(Matrix44f) == sizeof(float[16]));
|
||||
auto* m = reinterpret_cast<Matrix44f*>(&fvals_[fvals_index_]);
|
||||
fvals_index_ += 16;
|
||||
return m;
|
||||
}
|
||||
|
||||
auto GetMatrices(int* count) -> Matrix44f* {
|
||||
assert(finalized_);
|
||||
assert(ivals_index_ < ivals_.size());
|
||||
*count = ivals_[ivals_index_++];
|
||||
int f_count = 16 * (*count);
|
||||
assert(fvals_index_ + f_count <= fvals_.size());
|
||||
static_assert(sizeof(Matrix44f[2]) == sizeof(float[16][2]));
|
||||
auto* m = reinterpret_cast<Matrix44f*>(&fvals_[fvals_index_]);
|
||||
fvals_index_ += f_count;
|
||||
return m;
|
||||
}
|
||||
|
||||
auto GetModel() -> const ModelData* {
|
||||
assert(finalized_);
|
||||
assert(models_index_ < models_.size());
|
||||
return models_[models_index_++];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto GetMeshRendererData() -> T* {
|
||||
assert(finalized_);
|
||||
assert(mesh_datas_index_ < mesh_datas_.size());
|
||||
T* m;
|
||||
m = static_cast<T*>(mesh_datas_[mesh_datas_index_]->renderer_data());
|
||||
assert(m);
|
||||
assert(
|
||||
m == dynamic_cast<T*>(mesh_datas_[mesh_datas_index_]->renderer_data()));
|
||||
mesh_datas_index_++;
|
||||
return m;
|
||||
}
|
||||
|
||||
auto GetTexture() -> const TextureData* {
|
||||
assert(finalized_);
|
||||
assert(textures_index_ < textures_.size());
|
||||
return textures_[textures_index_++];
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
commands_.resize(0);
|
||||
fvals_.resize(0);
|
||||
ivals_.resize(0);
|
||||
models_.resize(0);
|
||||
textures_.resize(0);
|
||||
mesh_datas_.resize(0);
|
||||
finalized_ = false;
|
||||
}
|
||||
|
||||
// Call once done writing to buffer.
|
||||
void Finalize() {
|
||||
assert(!finalized_);
|
||||
finalized_ = true;
|
||||
}
|
||||
|
||||
// Set up iterators to read back data.
|
||||
void ReadBegin() {
|
||||
assert(finalized_);
|
||||
commands_index_ = 0;
|
||||
fvals_index_ = 0;
|
||||
ivals_index_ = 0;
|
||||
models_index_ = 0;
|
||||
textures_index_ = 0;
|
||||
mesh_datas_index_ = 0;
|
||||
}
|
||||
auto has_draw_commands() const -> bool {
|
||||
for (auto& command : commands_) {
|
||||
switch (command) {
|
||||
case Command::kDrawModel:
|
||||
case Command::kDrawModelInstanced:
|
||||
case Command::kDrawMesh:
|
||||
case Command::kDrawScreenQuad:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check: Makes sure all buffer iterators are at their end.
|
||||
auto IsEmpty() -> bool {
|
||||
return (
|
||||
(commands_index_ == commands_.size()) && (fvals_index_ == fvals_.size())
|
||||
&& (ivals_index_ == ivals_.size()) && (models_index_ == models_.size())
|
||||
&& (textures_index_ == textures_.size())
|
||||
&& (mesh_datas_index_ == mesh_datas_.size()));
|
||||
}
|
||||
|
||||
auto frame_def() const -> FrameDef* {
|
||||
assert(frame_def_);
|
||||
return frame_def_;
|
||||
}
|
||||
|
||||
void set_frame_def(FrameDef* f) { frame_def_ = f; }
|
||||
|
||||
private:
|
||||
std::vector<Command> commands_;
|
||||
std::vector<float> fvals_;
|
||||
std::vector<int> ivals_;
|
||||
std::vector<ModelData*> models_{};
|
||||
std::vector<TextureData*> textures_{};
|
||||
std::vector<MeshData*> mesh_datas_{};
|
||||
unsigned int commands_index_{};
|
||||
unsigned int fvals_index_{};
|
||||
unsigned int ivals_index_{};
|
||||
unsigned int models_index_{};
|
||||
unsigned int textures_index_{};
|
||||
unsigned int mesh_datas_index_{};
|
||||
bool finalized_{};
|
||||
FrameDef* frame_def_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_RENDER_COMMAND_BUFFER_H_
|
||||
507
src/ballistica/graphics/render_pass.cc
Normal file
507
src/ballistica/graphics/render_pass.cc
Normal file
@ -0,0 +1,507 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/render_pass.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
// Turn this off to not draw any transparent stuff.
|
||||
#define DRAW_TRANSPARENT 1
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
const float kCamNearClip = 4.0f;
|
||||
const float kCamFarClip = 1000.0f;
|
||||
|
||||
RenderPass::RenderPass(RenderPass::Type type_in, FrameDef* frame_def_in)
|
||||
: type_(type_in), frame_def_(frame_def_in) {
|
||||
// Create/init our command buffers.
|
||||
if (UsesWorldLists()) {
|
||||
for (auto& command : commands_) {
|
||||
command = std::make_unique<RenderCommandBuffer>();
|
||||
|
||||
// FIXME: Could just pass in constructor?
|
||||
command->set_frame_def(frame_def_);
|
||||
}
|
||||
} else {
|
||||
commands_flat_transparent_ = std::make_unique<RenderCommandBuffer>();
|
||||
commands_flat_transparent_->set_frame_def(frame_def_);
|
||||
commands_flat_ = std::make_unique<RenderCommandBuffer>();
|
||||
|
||||
// FIXME: Could just pass in constructor?
|
||||
commands_flat_->set_frame_def(frame_def_);
|
||||
}
|
||||
}
|
||||
|
||||
RenderPass::~RenderPass() = default;
|
||||
|
||||
void RenderPass::Render(RenderTarget* render_target, bool transparent) {
|
||||
assert(InGraphicsThread());
|
||||
|
||||
if (explicit_bool(!DRAW_TRANSPARENT) && transparent) {
|
||||
return;
|
||||
}
|
||||
#undef DRAW_TRANSPRENT
|
||||
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
|
||||
// Set up camera & depth.
|
||||
switch (type()) {
|
||||
case Type::kBeautyPass: {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
// If this changes, make sure to change
|
||||
// it before _drawCameraBuffer() too.
|
||||
|
||||
// FIXME:
|
||||
// If we're drawing our cam into its own buffer we could technically
|
||||
// use its full depth range ...otherwise we need to share with the
|
||||
// other onscreen elements (but maybe its good to use the limited
|
||||
// range regardless to make sure we can)
|
||||
renderer->SetDepthRange(kBackingDepth3, kBackingDepth4);
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
|
||||
tex_project_matrix_ = g_graphics_server->GetModelViewProjectionMatrix();
|
||||
model_view_matrix_ = g_graphics_server->model_view_matrix();
|
||||
model_view_projection_matrix_ =
|
||||
g_graphics_server->GetModelViewProjectionMatrix();
|
||||
|
||||
// Store our matrix to get things in screen space.
|
||||
tex_project_matrix_ *= Matrix44fScale(0.5f);
|
||||
tex_project_matrix_ *= Matrix44fTranslate(0.5f, 0.5f, 0);
|
||||
break;
|
||||
}
|
||||
case Type::kOverlay3DPass: {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
|
||||
// If we drew the world directly to the screen we need to use a depth
|
||||
// range that lies fully in front of that range so we don't get obscured
|
||||
// by any of the world.
|
||||
|
||||
// However if we drew the world to an offscreen buffer this isn't a
|
||||
// problem; nothing exists in that range. In that case lets draw to the
|
||||
// same range so we can do easy depth comparisons to the offscreen world's
|
||||
// depth (for overlay fog, blurs, etc)
|
||||
|
||||
// Use same region as world.
|
||||
if (renderer->has_camera_render_target()) {
|
||||
// Use beauty-pass depth region
|
||||
renderer->SetDepthRange(kBackingDepth3, kBackingDepth4);
|
||||
} else {
|
||||
// Use region in front of world
|
||||
renderer->SetDepthRange(kBackingDepth2, kBackingDepth3);
|
||||
}
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
break;
|
||||
}
|
||||
case Type::kVRCoverPass: {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
|
||||
// We use the front depth range where the overlays would
|
||||
// live in the non-vr path.
|
||||
renderer->SetDepthRange(kBackingDepth1, kBackingDepth2);
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
break;
|
||||
}
|
||||
case Type::kBlitPass: {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
|
||||
// We render into a little sliver of the depth buffer in the
|
||||
// back just in front of the backing blit.
|
||||
assert(renderer->has_camera_render_target());
|
||||
renderer->SetDepthRange(kBackingDepth4, kBackingDepth5);
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
break;
|
||||
}
|
||||
case Type::kBeautyPassBG: {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
renderer->SetDepthRange(kBackingDepth3, kBackingDepth4);
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
break;
|
||||
}
|
||||
case Type::kOverlayPass:
|
||||
case Type::kOverlayFrontPass:
|
||||
case Type::kOverlayFixedPass:
|
||||
case Type::kOverlayFlatPass: {
|
||||
// In vr mode we draw the flat-overlay into its own buffer so can use
|
||||
// the full depth range (shouldn't matter but why not?...) shouldn't.
|
||||
if (IsVRMode()) {
|
||||
// In vr mode, our overlay-flat pass is ortho-projected
|
||||
// while our regular overlay is just rendered in world space using
|
||||
// the vr-overlay matrix.
|
||||
if (type() == Type::kOverlayFlatPass) {
|
||||
g_graphics_server->ModelViewReset();
|
||||
renderer->SetDepthRange(0, 1); // we can use full depth range!!
|
||||
float amt = 0.5f * kVRBorder;
|
||||
float w = virtual_width();
|
||||
float h = virtual_height();
|
||||
g_graphics_server->SetOrthoProjection(
|
||||
-amt * w, (1.0f + amt) * w, -amt * h, (1.0f + amt) * h, -1, 1);
|
||||
} else {
|
||||
g_graphics_server->SetCamera(cam_pos_, cam_target_, cam_up_);
|
||||
// We set the same depth ranges as the overlay-3d pass since
|
||||
// we're essentially doing the same thing. See explanation in the
|
||||
// overlay-3d case above the one difference is that we split the
|
||||
// range between our fixed overlay and our regular overlay passes
|
||||
// (we want fixed-overlay stuff on bottom).
|
||||
if (renderer->has_camera_render_target()) {
|
||||
if (type() == Type::kOverlayFrontPass) {
|
||||
renderer->SetDepthRange(kBackingDepth3, kBackingDepth3B);
|
||||
} else if (type() == Type::kOverlayPass) {
|
||||
renderer->SetDepthRange(kBackingDepth3B, kBackingDepth3C);
|
||||
} else {
|
||||
renderer->SetDepthRange(kBackingDepth3C, kBackingDepth4);
|
||||
}
|
||||
} else {
|
||||
if (type() == Type::kOverlayFrontPass) {
|
||||
renderer->SetDepthRange(kBackingDepth2, kBackingDepth2B);
|
||||
} else if (type() == Type::kOverlayPass) {
|
||||
renderer->SetDepthRange(kBackingDepth2B, kBackingDepth2C);
|
||||
} else {
|
||||
renderer->SetDepthRange(kBackingDepth2C, kBackingDepth3);
|
||||
}
|
||||
}
|
||||
SetFrustum(cam_near_clip_, cam_far_clip_);
|
||||
|
||||
// Now move to wherever our 2d plane in space is to start with.
|
||||
if (type() == Type::kOverlayPass
|
||||
|| type() == Type::kOverlayFrontPass) {
|
||||
g_graphics_server->MultMatrix(
|
||||
frame_def()->vr_overlay_screen_matrix());
|
||||
} else {
|
||||
assert(type() == Type::kOverlayFixedPass);
|
||||
g_graphics_server->MultMatrix(
|
||||
frame_def()->vr_overlay_screen_matrix_fixed());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Nn non-vr mode both our overlays are just ortho projected.
|
||||
g_graphics_server->ModelViewReset();
|
||||
if (type() == Type::kOverlayFrontPass) {
|
||||
renderer->SetDepthRange(kBackingDepth1, kBackingDepth1B);
|
||||
} else {
|
||||
renderer->SetDepthRange(kBackingDepth1B, kBackingDepth2);
|
||||
}
|
||||
if (g_graphics_server->tv_border()) {
|
||||
float amt = 0.5f * kTVBorder;
|
||||
float w = virtual_width();
|
||||
float h = virtual_height();
|
||||
g_graphics_server->SetOrthoProjection(
|
||||
-amt * w, (1.0f + amt) * w, -amt * h, (1.0f + amt) * h, -1, 1);
|
||||
} else {
|
||||
g_graphics_server->SetOrthoProjection(0, virtual_width(), 0,
|
||||
virtual_height(), -1, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::kLightPass:
|
||||
case Type::kLightShadowPass: {
|
||||
// Ortho shadows.
|
||||
if (renderer->shadow_ortho()) {
|
||||
g_graphics_server->ModelViewReset();
|
||||
g_graphics_server->SetOrthoProjection(-12, 12, -12, 12, 10, 100);
|
||||
g_graphics_server->Translate(Vector3f(0, 0, renderer->light_tz()));
|
||||
g_graphics_server->Rotate(80, Vector3f(1.0f, 0, 0));
|
||||
const Vector3f& soffs = renderer->shadow_offset();
|
||||
g_graphics_server->Translate(Vector3f(-soffs.x, -soffs.y, -soffs.z));
|
||||
g_graphics_server->scale(Vector3f(1.0f / renderer->shadow_scale_x(),
|
||||
1.0f,
|
||||
1.0f / renderer->shadow_scale_z()));
|
||||
} else {
|
||||
float fovy = 45.0f * kPi / 180.0f;
|
||||
float fovx = fovy;
|
||||
float near_val = 10;
|
||||
float far_val = 100;
|
||||
float x = near_val * tanf(fovx);
|
||||
float y = near_val * tanf(fovy);
|
||||
|
||||
g_graphics_server->SetProjectionMatrix(
|
||||
Matrix44fFrustum(-x, x, -y, y, near_val, far_val));
|
||||
g_graphics_server->ModelViewReset();
|
||||
g_graphics_server->Translate(
|
||||
Vector3f(0.0f, 0.0f, renderer->light_tz()));
|
||||
g_graphics_server->Rotate(renderer->light_pitch(),
|
||||
Vector3f(1.0f, 0.0f, 0.0f));
|
||||
g_graphics_server->Rotate(renderer->light_heading(),
|
||||
Vector3f(0.0f, 1.0f, 0.0f));
|
||||
const Vector3f& soffs = renderer->shadow_offset();
|
||||
|
||||
// Well, this is slightly terrifying; '-soffs' is causing crashes
|
||||
// here but multing by -1.000001f works.
|
||||
// (generally just on android 4.3 on atom processors)
|
||||
g_graphics_server->Translate(Vector3f(
|
||||
-1.000001f * soffs.x, -1.000001f * soffs.y, -1.000001f * soffs.z));
|
||||
}
|
||||
|
||||
// ...now store the matrix we'll use to project this as a texture
|
||||
// FIXME: most of these calculations could be cached instead of
|
||||
// redoing them every pass
|
||||
tex_project_matrix_ = g_graphics_server->GetModelViewProjectionMatrix();
|
||||
model_view_matrix_ = g_graphics_server->model_view_matrix();
|
||||
model_view_projection_matrix_ =
|
||||
g_graphics_server->GetModelViewProjectionMatrix();
|
||||
tex_project_matrix_ *= Matrix44fScale(0.5f);
|
||||
tex_project_matrix_ *= Matrix44fTranslate(0.5f, 0.5f, 0);
|
||||
g_graphics_server->SetLightShadowProjectionMatrix(tex_project_matrix_);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
|
||||
// Some passes draw stuff into the world bucketed by type.
|
||||
if (UsesWorldLists()) {
|
||||
// For opaque stuff, render non-reflected(above-ground),
|
||||
// then reflected(below-ground) stuff (less overdraw that way)
|
||||
// for transparent stuff we do the opposite so we get better layering.
|
||||
ReflectionSubPass reflection_sub_passes[2];
|
||||
if (transparent) {
|
||||
reflection_sub_passes[0] = ReflectionSubPass::kMirrored;
|
||||
reflection_sub_passes[1] = ReflectionSubPass::kRegular;
|
||||
} else {
|
||||
reflection_sub_passes[0] = ReflectionSubPass::kRegular;
|
||||
reflection_sub_passes[1] = ReflectionSubPass::kMirrored;
|
||||
}
|
||||
|
||||
for (auto reflection_sub_pass : reflection_sub_passes) {
|
||||
bool doing_reflection = false;
|
||||
if (reflection_sub_pass == ReflectionSubPass::kMirrored) {
|
||||
// Only actually draw reflection pass if quality >= high
|
||||
// and floor-reflections are on.
|
||||
if (floor_reflection()
|
||||
&& frame_def()->quality() >= GraphicsQuality::kHigher) {
|
||||
doing_reflection = true;
|
||||
renderer->set_drawing_reflection(true);
|
||||
g_graphics_server->PushTransform();
|
||||
Matrix44f m = Matrix44fScale(Vector3f(1, -1, 1));
|
||||
g_graphics_server->MultMatrix(m);
|
||||
renderer->FlipCullFace(); // Flip into reflection drawing.
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
renderer->set_drawing_reflection(false);
|
||||
}
|
||||
|
||||
// Render everything with the same material together to
|
||||
// minimize gl state changes.
|
||||
|
||||
// Organize shaders that are likely to be occluding other stuff first.
|
||||
ShadingType component_types_opaque[] = {
|
||||
ShadingType::kSimpleColor,
|
||||
ShadingType::kSimpleTexture,
|
||||
ShadingType::kSimpleTextureModulated,
|
||||
ShadingType::kSimpleTextureModulatedColorized,
|
||||
ShadingType::kSimpleTextureModulatedColorized2,
|
||||
ShadingType::kSimpleTextureModulatedColorized2Masked,
|
||||
ShadingType::kObjectReflectLightShadow,
|
||||
ShadingType::kObjectLightShadow,
|
||||
ShadingType::kObjectReflect,
|
||||
ShadingType::kObject,
|
||||
ShadingType::kObjectReflectLightShadowDoubleSided,
|
||||
ShadingType::kObjectReflectLightShadowColorized,
|
||||
ShadingType::kObjectReflectLightShadowColorized2,
|
||||
ShadingType::kObjectReflectLightShadowAdd,
|
||||
ShadingType::kObjectReflectLightShadowAddColorized,
|
||||
ShadingType::kObjectReflectLightShadowAddColorized2};
|
||||
|
||||
ShadingType component_types_transparent[] = {
|
||||
ShadingType::kSimpleColorTransparent,
|
||||
ShadingType::kSimpleColorTransparentDoubleSided,
|
||||
ShadingType::kObjectTransparent,
|
||||
ShadingType::kObjectLightShadowTransparent,
|
||||
ShadingType::kObjectReflectTransparent,
|
||||
ShadingType::kObjectReflectAddTransparent,
|
||||
ShadingType::kSimpleTextureModulatedTransparent,
|
||||
ShadingType::kSimpleTextureModulatedTransFlatness,
|
||||
ShadingType::kSimpleTextureModulatedTransparentDoubleSided,
|
||||
ShadingType::kSimpleTextureModulatedTransparentColorized,
|
||||
ShadingType::kSimpleTextureModulatedTransparentColorized2,
|
||||
ShadingType::kSimpleTextureModulatedTransparentColorized2Masked,
|
||||
ShadingType::kSimpleTextureModulatedTransparentShadow,
|
||||
ShadingType::kSimpleTexModulatedTransShadowFlatness,
|
||||
ShadingType::kSimpleTextureModulatedTransparentGlow,
|
||||
ShadingType::kSimpleTextureModulatedTransparentGlowMaskUV2,
|
||||
ShadingType::kSmoke,
|
||||
ShadingType::kSprite};
|
||||
|
||||
ShadingType* component_types;
|
||||
int component_type_count;
|
||||
if (transparent) {
|
||||
component_types = component_types_transparent;
|
||||
component_type_count =
|
||||
(sizeof(component_types_transparent) / sizeof(ShadingType));
|
||||
} else {
|
||||
component_types = component_types_opaque;
|
||||
component_type_count =
|
||||
(sizeof(component_types_opaque) / sizeof(ShadingType));
|
||||
}
|
||||
|
||||
for (int c = 0; c < component_type_count; c++) {
|
||||
renderer->ProcessRenderCommandBuffer(
|
||||
commands_[static_cast<int>(component_types[c])].get(), *this,
|
||||
render_target);
|
||||
}
|
||||
|
||||
if (doing_reflection) {
|
||||
renderer->FlipCullFace(); // Flip out of reflection drawing.
|
||||
g_graphics_server->PopTransform();
|
||||
}
|
||||
}
|
||||
renderer->set_drawing_reflection(false);
|
||||
} else {
|
||||
// ..and some passes draw flat lists in order added.
|
||||
if (transparent) {
|
||||
renderer->ProcessRenderCommandBuffer(commands_flat_transparent_.get(),
|
||||
*this, render_target);
|
||||
} else {
|
||||
renderer->ProcessRenderCommandBuffer(commands_flat_.get(), *this,
|
||||
render_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPass::SetCamera(
|
||||
const Vector3f& pos, const Vector3f& target, const Vector3f& up,
|
||||
float near_clip_in, float far_clip_in, float fov_x_in, float fov_y_in,
|
||||
bool use_fov_tangents, float fov_tan_l, float fov_tan_r, float fov_tan_b,
|
||||
float fov_tan_t, const std::vector<Vector3f>& area_of_interest_points) {
|
||||
cam_pos_ = pos;
|
||||
cam_target_ = target;
|
||||
cam_up_ = up;
|
||||
cam_near_clip_ = near_clip_in;
|
||||
cam_far_clip_ = far_clip_in;
|
||||
cam_use_fov_tangents_ = use_fov_tangents;
|
||||
cam_fov_x_ = fov_x_in;
|
||||
cam_fov_y_ = fov_y_in;
|
||||
cam_fov_l_tan_ = fov_tan_l;
|
||||
cam_fov_r_tan_ = fov_tan_r;
|
||||
cam_fov_b_tan_ = fov_tan_b;
|
||||
cam_fov_t_tan_ = fov_tan_t;
|
||||
cam_area_of_interest_points_ = area_of_interest_points;
|
||||
}
|
||||
|
||||
void RenderPass::Reset() {
|
||||
virtual_width_ = 0;
|
||||
virtual_height_ = 0;
|
||||
physical_width_ = 0;
|
||||
physical_height_ = 0;
|
||||
floor_reflection_ = false;
|
||||
cam_pos_ = {0.0f, 0.0f, 0.0f};
|
||||
cam_target_ = {0.0f, 0.0f, 1.0f};
|
||||
cam_up_ = {0.0f, 1.0f, 0.0f};
|
||||
cam_near_clip_ = kCamNearClip;
|
||||
cam_far_clip_ = kCamFarClip;
|
||||
cam_fov_x_ = -1.0f;
|
||||
cam_fov_y_ = 40.0f;
|
||||
tex_project_matrix_ = kMatrix44fIdentity;
|
||||
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
|
||||
// Figure our our width/height for drawing commands to reference
|
||||
// (we cant wait until the drawing is actually occurring because
|
||||
// that happens in another thread later)
|
||||
switch (type()) {
|
||||
case Type::kBeautyPass:
|
||||
case Type::kBeautyPassBG:
|
||||
case Type::kOverlay3DPass:
|
||||
case Type::kOverlayPass:
|
||||
case Type::kOverlayFrontPass:
|
||||
case Type::kOverlayFlatPass:
|
||||
case Type::kVRCoverPass:
|
||||
case Type::kOverlayFixedPass:
|
||||
case Type::kBlitPass:
|
||||
physical_width_ = g_graphics->screen_pixel_width();
|
||||
physical_height_ = g_graphics->screen_pixel_height();
|
||||
break;
|
||||
case Type::kLightPass:
|
||||
physical_width_ = physical_height_ =
|
||||
static_cast<float>(renderer->shadow_res()) / kLightResDiv;
|
||||
break;
|
||||
case Type::kLightShadowPass:
|
||||
physical_width_ = physical_height_ =
|
||||
static_cast<float>(renderer->shadow_res());
|
||||
break;
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
|
||||
// By default, logical width matches physical width, but for overlay passes
|
||||
// it can be independent.
|
||||
switch (type()) {
|
||||
case Type::kOverlayPass:
|
||||
case Type::kOverlayFrontPass:
|
||||
case Type::kOverlayFixedPass:
|
||||
case Type::kOverlayFlatPass:
|
||||
virtual_width_ = g_graphics->screen_virtual_width();
|
||||
virtual_height_ = g_graphics->screen_virtual_height();
|
||||
break;
|
||||
default:
|
||||
virtual_width_ = physical_width_;
|
||||
virtual_height_ = physical_height_;
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear the command buffers this pass cares about.
|
||||
if (UsesWorldLists()) {
|
||||
for (auto& command : commands_) {
|
||||
command->Reset();
|
||||
}
|
||||
} else {
|
||||
commands_flat_->Reset();
|
||||
commands_flat_transparent_->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPass::SetFrustum(float near_val, float far_val) {
|
||||
assert(InGraphicsThread());
|
||||
// If we're using fov-tangents:
|
||||
if (cam_use_fov_tangents_) {
|
||||
float l = near_val * cam_fov_l_tan_;
|
||||
float r = near_val * cam_fov_r_tan_;
|
||||
float t = near_val * cam_fov_t_tan_;
|
||||
float b = near_val * cam_fov_b_tan_;
|
||||
projection_matrix_ = Matrix44fFrustum(-l, r, -b, t, near_val, far_val);
|
||||
} else {
|
||||
// Old angle-based stuff:
|
||||
float x;
|
||||
float angleY = (cam_fov_y_ / 2.0f) * kPi / 180.0f;
|
||||
float y = near_val * tanf(angleY);
|
||||
|
||||
// Fov-x < 0 implies to use aspect ratio.
|
||||
if (cam_fov_x_ > 0.0f) {
|
||||
float angleX = (cam_fov_x_ / 2.0f) * kPi / 180.0f;
|
||||
x = near_val * tanf(angleX);
|
||||
} else {
|
||||
x = y * GetPhysicalAspectRatio();
|
||||
}
|
||||
projection_matrix_ = Matrix44fFrustum(-x, x, -y, y, near_val, far_val);
|
||||
}
|
||||
g_graphics_server->SetProjectionMatrix(projection_matrix_);
|
||||
}
|
||||
|
||||
void RenderPass::Finalize() {
|
||||
if (UsesWorldLists()) {
|
||||
for (auto& command : commands_) {
|
||||
command->Finalize();
|
||||
}
|
||||
} else {
|
||||
commands_flat_->Finalize();
|
||||
commands_flat_transparent_->Finalize();
|
||||
}
|
||||
}
|
||||
|
||||
auto RenderPass::HasDrawCommands() const -> bool {
|
||||
if (UsesWorldLists()) {
|
||||
throw Exception();
|
||||
} else {
|
||||
return (commands_flat_transparent_->has_draw_commands()
|
||||
|| commands_flat_->has_draw_commands());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
167
src/ballistica/graphics/render_pass.h
Normal file
167
src/ballistica/graphics/render_pass.h
Normal file
@ -0,0 +1,167 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_RENDER_PASS_H_
|
||||
#define BALLISTICA_GRAPHICS_RENDER_PASS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/math/matrix44f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// A drawing context for one pass. This can be a render to the screen, a shadow
|
||||
// pass, a window, etc.
|
||||
class RenderPass {
|
||||
public:
|
||||
enum class ReflectionSubPass { kRegular, kMirrored };
|
||||
enum class Type {
|
||||
kLightShadowPass,
|
||||
kLightPass,
|
||||
kBeautyPass,
|
||||
kBeautyPassBG,
|
||||
kBlitPass,
|
||||
// Standard 2d overlay stuff. May be drawn in 2d or on a plane in 3d
|
||||
// space (in vr). In VR, each of these elements are drawn individually
|
||||
// and can thus have their own depth. also in VR this overlay repositions
|
||||
// itself per level; use kOverlayFixedPass for items that shouldn't.
|
||||
// this overlay may be obscured by UI. Use OVERLAY_FRONT_PASS if you need
|
||||
// things to show up in front of UI.
|
||||
kOverlayPass,
|
||||
// Just like kOverlayPass but guaranteed to draw in front of UI.
|
||||
kOverlayFrontPass,
|
||||
// Actually drawn in regular 3d space - for life bars, names, etc that
|
||||
// need to overlay regular 3d stuff but exist in the world.
|
||||
kOverlay3DPass,
|
||||
// Only used in VR - overlay stuff drawn into a flat 2d texture so that
|
||||
// scissoring/etc works (the UI uses this).
|
||||
kOverlayFlatPass,
|
||||
/// Only used in VR - stuff that needs to cover absolutely everything
|
||||
/// else (like the 3d wipe fade).
|
||||
kVRCoverPass,
|
||||
// Only used in VR - overlay elements that should always be fixed in space.
|
||||
kOverlayFixedPass
|
||||
};
|
||||
|
||||
RenderPass(Type type_in, FrameDef* frame_def);
|
||||
virtual ~RenderPass();
|
||||
|
||||
auto type() const -> Type { return type_; }
|
||||
|
||||
// The physical size of the drawing surface.
|
||||
auto physical_width() const -> float { return physical_width_; }
|
||||
auto physical_height() const -> float { return physical_height_; }
|
||||
|
||||
// The virtual size of the drawing surface.
|
||||
// This may or may not have anything to do with the physical size
|
||||
// (for instance the overlay pass in VR has its own bounds which
|
||||
// is completely independent of the physical surface it gets drawn into).
|
||||
auto virtual_width() const -> float { return virtual_width_; }
|
||||
auto virtual_height() const -> float { return virtual_height_; }
|
||||
|
||||
// Should objects be rendered 'underground' in this pass?
|
||||
auto floor_reflection() const -> bool { return floor_reflection_; }
|
||||
void set_floor_reflection(bool val) { floor_reflection_ = val; }
|
||||
auto GetPhysicalAspectRatio() const -> float {
|
||||
return physical_width() / physical_height();
|
||||
}
|
||||
void SetCamera(const Vector3f& pos, const Vector3f& target,
|
||||
const Vector3f& up, float near_clip, float far_clip,
|
||||
float fov_x, // Set to -1 for auto.
|
||||
float fov_y, bool use_fov_tangents, float fov_tan_l,
|
||||
float fov_tan_r, float fov_tan_b, float fov_tan_t,
|
||||
const std::vector<Vector3f>& area_of_interest_points);
|
||||
auto frame_def() const -> FrameDef* { return frame_def_; }
|
||||
void Render(RenderTarget* t, bool transparent);
|
||||
auto tex_project_matrix() const -> const Matrix44f& {
|
||||
return tex_project_matrix_;
|
||||
}
|
||||
auto projection_matrix() const -> const Matrix44f& {
|
||||
return projection_matrix_;
|
||||
}
|
||||
auto model_view_matrix() const -> const Matrix44f& {
|
||||
return model_view_matrix_;
|
||||
}
|
||||
auto model_view_projection_matrix() const -> const Matrix44f& {
|
||||
return model_view_projection_matrix_;
|
||||
}
|
||||
auto HasDrawCommands() const -> bool;
|
||||
void Finalize();
|
||||
void Reset();
|
||||
|
||||
// Whether this pass draws stuff from the per-shader command lists
|
||||
auto UsesWorldLists() const -> bool {
|
||||
switch (type()) {
|
||||
case Type::kBeautyPass:
|
||||
case Type::kBeautyPassBG:
|
||||
return true;
|
||||
case Type::kOverlayPass:
|
||||
case Type::kOverlayFrontPass:
|
||||
case Type::kOverlay3DPass:
|
||||
case Type::kVRCoverPass:
|
||||
case Type::kOverlayFlatPass:
|
||||
case Type::kOverlayFixedPass:
|
||||
case Type::kBlitPass:
|
||||
case Type::kLightPass:
|
||||
case Type::kLightShadowPass:
|
||||
return false;
|
||||
default:
|
||||
throw Exception();
|
||||
}
|
||||
}
|
||||
auto commands_flat() const -> RenderCommandBuffer* {
|
||||
return commands_flat_.get();
|
||||
}
|
||||
auto commands_flat_transparent() const -> RenderCommandBuffer* {
|
||||
return commands_flat_transparent_.get();
|
||||
}
|
||||
auto GetCommands(ShadingType type) const -> RenderCommandBuffer* {
|
||||
return commands_[static_cast<int>(type)].get();
|
||||
}
|
||||
|
||||
auto cam_area_of_interest_points() const -> const std::vector<Vector3f>& {
|
||||
return cam_area_of_interest_points_;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetFrustum(float near_val, float far_val);
|
||||
|
||||
// Our pass holds sets of draw-commands bucketed by section and
|
||||
// component-type.
|
||||
std::unique_ptr<RenderCommandBuffer>
|
||||
commands_[static_cast<int>(ShadingType::kCount)];
|
||||
std::unique_ptr<RenderCommandBuffer> commands_flat_;
|
||||
std::unique_ptr<RenderCommandBuffer> commands_flat_transparent_;
|
||||
Vector3f cam_pos_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f cam_target_{0.0f, 0.0f, 0.0f};
|
||||
Vector3f cam_up_{0.0f, 0.0f, 0.0f};
|
||||
float cam_near_clip_{};
|
||||
float cam_far_clip_{};
|
||||
float cam_fov_x_{};
|
||||
float cam_fov_y_{};
|
||||
|
||||
// We can now alternately supply left, right, top, bottom frustum tangents.
|
||||
bool cam_use_fov_tangents_{};
|
||||
float cam_fov_l_tan_{1.0f};
|
||||
float cam_fov_r_tan_{1.0f};
|
||||
float cam_fov_t_tan_{1.0f};
|
||||
float cam_fov_b_tan_{1.0f};
|
||||
std::vector<Vector3f> cam_area_of_interest_points_;
|
||||
Type type_{};
|
||||
|
||||
// For lights/shadows.
|
||||
Matrix44f tex_project_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f projection_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f model_view_matrix_{kMatrix44fIdentity};
|
||||
Matrix44f model_view_projection_matrix_{kMatrix44fIdentity};
|
||||
bool floor_reflection_{};
|
||||
FrameDef* frame_def_{};
|
||||
float physical_width_{};
|
||||
float physical_height_{};
|
||||
float virtual_width_{};
|
||||
float virtual_height_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_RENDER_PASS_H_
|
||||
84
src/ballistica/graphics/render_target.cc
Normal file
84
src/ballistica/graphics/render_target.cc
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/graphics/render_target.h"
|
||||
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
RenderTarget::RenderTarget(Type type) : type_(type) {
|
||||
assert(InGraphicsThread());
|
||||
}
|
||||
|
||||
RenderTarget::~RenderTarget() = default;
|
||||
|
||||
void RenderTarget::ScreenSizeChanged() {
|
||||
assert(type_ == Type::kScreen);
|
||||
physical_width_ = g_graphics_server->screen_pixel_width();
|
||||
physical_height_ = g_graphics_server->screen_pixel_height();
|
||||
}
|
||||
|
||||
auto RenderTarget::GetScissorX(float x) const -> float {
|
||||
if (IsVRMode()) {
|
||||
// map -0.05f to 1.1f in logical coordinates to 0 to 1 physical ones
|
||||
float res_x_virtual = g_graphics_server->screen_virtual_width();
|
||||
return physical_width_
|
||||
* (((x / res_x_virtual) + (kVRBorder * 0.5f)) / (1.0f + kVRBorder));
|
||||
} else {
|
||||
if (g_graphics_server->tv_border()) {
|
||||
// map -0.05f to 1.1f in logical coordinates to 0 to 1 physical ones
|
||||
float res_x_virtual = g_graphics_server->screen_virtual_width();
|
||||
return physical_width_
|
||||
* (((x / res_x_virtual) + (kTVBorder * 0.5f))
|
||||
/ (1.0f + kTVBorder));
|
||||
} else {
|
||||
return (physical_width_ / g_graphics_server->screen_virtual_width()) * x;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto RenderTarget::GetScissorY(float y) const -> float {
|
||||
if (IsVRMode()) {
|
||||
// map -0.05f to 1.1f in logical coordinates to 0 to 1 physical ones
|
||||
float res_y_virtual = g_graphics_server->screen_virtual_height();
|
||||
return physical_height_
|
||||
* (((y / res_y_virtual) + (kVRBorder * 0.5f)) / (1.0f + kVRBorder));
|
||||
} else {
|
||||
if (g_graphics_server->tv_border()) {
|
||||
// map -0.05f to 1.1f in logical coordinates to 0 to 1 physical ones
|
||||
float res_y_virtual = g_graphics_server->screen_virtual_height();
|
||||
return physical_height_
|
||||
* (((y / res_y_virtual) + (kTVBorder * 0.5f))
|
||||
/ (1.0f + kTVBorder));
|
||||
} else {
|
||||
return (physical_height_ / g_graphics_server->screen_virtual_height())
|
||||
* y;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto RenderTarget::GetScissorScaleX() const -> float {
|
||||
if (IsVRMode()) {
|
||||
float f = physical_width_ / g_graphics_server->screen_virtual_width();
|
||||
return f / (1.0f + kVRBorder);
|
||||
} else {
|
||||
float f = physical_width_ / g_graphics_server->screen_virtual_width();
|
||||
if (g_graphics_server->tv_border()) {
|
||||
return f / (1.0f + kTVBorder);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
auto RenderTarget::GetScissorScaleY() const -> float {
|
||||
if (IsVRMode()) {
|
||||
float f = physical_height_ / g_graphics_server->screen_virtual_height();
|
||||
return f / (1.0f + kVRBorder);
|
||||
} else {
|
||||
float f = physical_height_ / g_graphics_server->screen_virtual_height();
|
||||
if (g_graphics_server->tv_border()) {
|
||||
return f / (1.0f + kTVBorder);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
47
src/ballistica/graphics/render_target.h
Normal file
47
src/ballistica/graphics/render_target.h
Normal file
@ -0,0 +1,47 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_GRAPHICS_RENDER_TARGET_H_
|
||||
#define BALLISTICA_GRAPHICS_RENDER_TARGET_H_
|
||||
|
||||
#include "ballistica/core/object.h"
|
||||
#include "ballistica/math/vector4f.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// Encapsulates framebuffers, main windows, etc.
|
||||
class RenderTarget : public Object {
|
||||
public:
|
||||
auto GetDefaultOwnerThread() const -> ThreadIdentifier override {
|
||||
return ThreadIdentifier::kMain;
|
||||
}
|
||||
enum class Type { kScreen, kFramebuffer };
|
||||
explicit RenderTarget(Type type);
|
||||
~RenderTarget() override;
|
||||
|
||||
// Clear depth, color, etc and get set to draw.
|
||||
virtual void DrawBegin(bool clear, float clear_r, float clear_g,
|
||||
float clear_b, float clear_a) = 0;
|
||||
void DrawBegin(bool clear,
|
||||
const Vector4f& clear_color = {0.0f, 0.0f, 0.0f, 1.0f}) {
|
||||
DrawBegin(clear, clear_color.x, clear_color.y, clear_color.z,
|
||||
clear_color.w);
|
||||
}
|
||||
|
||||
void ScreenSizeChanged();
|
||||
auto physical_width() const -> float { return physical_width_; }
|
||||
auto physical_height() const -> float { return physical_height_; }
|
||||
auto GetScissorScaleX() const -> float;
|
||||
auto GetScissorScaleY() const -> float;
|
||||
auto GetScissorX(float x) const -> float;
|
||||
auto GetScissorY(float y) const -> float;
|
||||
|
||||
protected:
|
||||
float physical_width_{};
|
||||
float physical_height_{};
|
||||
bool depth_{};
|
||||
Type type_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_GRAPHICS_RENDER_TARGET_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user