summaryrefslogtreecommitdiff
path: root/src/glplanet.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/glplanet.cc')
-rw-r--r--src/glplanet.cc170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/glplanet.cc b/src/glplanet.cc
new file mode 100644
index 0000000..69f4ac3
--- /dev/null
+++ b/src/glplanet.cc
@@ -0,0 +1,170 @@
+// Copyright 2021, 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "src/glplanet.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#include <chrono>
+#include <iostream>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "res/res.h"
+#include "src/egl.h"
+#include "src/gl/draw.h"
+#include "src/gl/init.h"
+#include "src/scene.h"
+#include "src/x/connection.h"
+#include "src/x/event.h"
+#include "src/x/rpc.h"
+#include "src/x/screen.h"
+#include "src/x/types.h"
+#include "third_party/abseil/absl/meta/type_traits.h"
+#include "third_party/abseil/absl/strings/string_view.h"
+#include "third_party/abseil/absl/types/span.h"
+#include "third_party/abseil/absl/types/variant.h"
+
+namespace glplanet {
+
+namespace {
+
+constexpr double kRadiansPerDegree = M_PI / 180.0;
+
+x::Connection ConnectToDisplaySpecifiedInEnvironment() {
+ const char* display_name = getenv("DISPLAY");
+ if (display_name == nullptr) {
+ throw std::runtime_error("DISPLAY unset");
+ }
+ return x::Connection(display_name);
+}
+
+} // namespace
+
+int Main(const MainOptions& options) {
+ x::Connection x = ConnectToDisplaySpecifiedInEnvironment();
+ egl::Display egl(x.AsXlibDisplay());
+
+ // Find an X visual that corresponds to our desired OpenGL settings.
+ egl::Configuration config =
+ egl.GetConfigurations(
+ {
+ {egl::kRedSize, 8},
+ {egl::kGreenSize, 8},
+ {egl::kBlueSize, 8},
+ {egl::kDepthSize, 1},
+ {egl::kRenderableTypeMask, egl::kOpenglBit},
+ {egl::kSampleBuffers, 1},
+ {egl::kSamples, 4},
+ },
+ /*limit=*/1)
+ .at(0);
+ x::Id visual = config.Get(egl::kNativeVisualId);
+
+ // Create the window we're going to display in, and tell X to map it. Set the
+ // window background to black so that if any newly exposed regions are briefly
+ // visible before being drawn on, the X server will fill them with the
+ // blackness of space.
+ std::vector<x::VoidCompletion> completions;
+ x::Screen screen = x.DefaultScreen();
+ x::Id window = x.GenerateId();
+ completions.push_back(x.CreateWindow({
+ .depth = screen.DepthOfVisual(visual),
+ .window = window,
+ .parent = screen.root(),
+ .x = 0,
+ .y = 0,
+ .width = 640,
+ .height = 480,
+ .border_width = 0,
+ .window_class = x::WindowClass::kInputOutput,
+ .visual_id = visual,
+ .background_pixel = screen.black_pixel(),
+ .event_mask = x::kExposure | x::kStructureNotify,
+ }));
+ completions.push_back(x.MapWindow(window));
+
+ // Create a rendering surface on the window and an OpenGL context to draw into
+ // that surface, and make that context current.
+ egl::Surface surface = egl.CreateWindowSurface(config, window);
+ egl::Context context = egl.CreateContext(config, egl::Api::kOpengl,
+ /*major=*/3,
+ /*minor=*/2);
+ egl::BindContext(surface, context);
+
+ gl::InitializeForThisThread();
+ gl::Enable(gl::Capability::kDepthTest);
+ gl::Enable(gl::Capability::kFramebufferSrgb);
+ // TODO(bbarenblat@gmail.com): Khronos says multisampling is turned on by
+ // default (see glEnable(3)). Do we actually need to turn it on explicitly?
+ gl::Enable(gl::Capability::kMultisample);
+ gl::SetClearColor(0.0, 0.0, 0.0, 1.0);
+
+ Scene scene({
+ .longitude_radians = options.longitude_degrees * kRadiansPerDegree,
+ .latitude_radians = options.latitude_degrees * kRadiansPerDegree,
+ });
+ scene.SetGlState();
+
+ // Ensure the window is mapped before we start drawing.
+ for (auto& completion : completions) {
+ std::move(completion).Check();
+ }
+
+ x::EventMonitor events(x);
+ int width = 640, height = 480;
+ while (true) {
+ // Handle any pending events.
+ int new_width = width, new_height = height;
+ while (absl::optional<x::Event> event = events.GetEventIfReady()) {
+ absl::visit(
+ [&](auto&& ev) {
+ using T = absl::decay_t<decltype(ev)>;
+ if constexpr (std::is_same_v<T, x::ConfigureNotifyEvent>) {
+ new_width = ev.width();
+ new_height = ev.height();
+ }
+ },
+ *event);
+ }
+
+ if (new_width != width || new_height != height) {
+ // Reset the viewport so the globe is centered and unsquashed.
+ int min_dimension = std::min(new_width, new_height);
+ int x_offset = (new_width - min_dimension) / 2;
+ int y_offset = (new_height - min_dimension) / 2;
+ gl::Clear({gl::GlBuffer::kColor});
+ gl::SetViewport(x_offset, y_offset, min_dimension, min_dimension);
+ width = new_width;
+ height = new_height;
+ }
+
+ gl::Clear({gl::GlBuffer::kDepth});
+ scene.Draw(std::chrono::system_clock::now());
+
+ // Wait for the frame to be displayed.
+ surface.SwapBuffers();
+
+ // TODO(bbarenblat@gmail.com): Actually get a target frame rate going based
+ // on the display size, rather than clamping to ~6 fps.
+ usleep(96'000);
+ }
+
+ return 0;
+}
+
+} // namespace glplanet