// 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 #include #include #include #include #include #include #include #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 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 event = events.GetEventIfReady()) { absl::visit( [&](auto&& ev) { using T = absl::decay_t; if constexpr (std::is_same_v) { 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