More C++ work

This commit is contained in:
Eric Froemling 2020-10-02 13:48:18 -05:00
parent 6f4820e054
commit 8c446dd0d6
193 changed files with 49975 additions and 504 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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_

View 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_

View 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

File diff suppressed because it is too large Load Diff

464
src/ballistica/game/game.h Normal file
View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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_

View 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_

View File

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

View 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_

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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_

View 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_

View 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_

View File

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

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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

View 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_

View 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

View 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