From d0e18bdb7924c71cdca8dd983711171d87ef28be Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Mon, 17 Jan 2022 23:12:32 -0500 Subject: glplanet, an OpenGL-based planetary renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit glplanet draws Earth like it currently appears from space, putting nighttime areas in shadow and daytime areas in light. It’s modeled after Xplanet (http://xplanet.sourceforge.net/), but whereas Xplanet is entirely a CPU-resident program, glplanet draws using OpenGL. It’s thus much less resource-intensive, particularly when using high-resolution textures. --- src/glplanet.cc | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/glplanet.cc (limited to 'src/glplanet.cc') 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 +#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 -- cgit v1.2.3