| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/x/visual_picker_glx.h" |
| |
| #include <algorithm> |
| #include <bitset> |
| #include <cstring> |
| #include <map> |
| #include <numeric> |
| #include <vector> |
| |
| #include "base/memory/singleton.h" |
| #include "ui/gfx/x/future.h" |
| |
| // These constants are obtained from GL/glx.h and GL/glxext.h. |
| constexpr uint32_t GLX_LEVEL = 3; |
| constexpr uint32_t GLX_DOUBLEBUFFER = 5; |
| constexpr uint32_t GLX_STEREO = 6; |
| constexpr uint32_t GLX_BUFFER_SIZE = 2; |
| constexpr uint32_t GLX_AUX_BUFFERS = 7; |
| constexpr uint32_t GLX_RED_SIZE = 8; |
| constexpr uint32_t GLX_GREEN_SIZE = 9; |
| constexpr uint32_t GLX_BLUE_SIZE = 10; |
| constexpr uint32_t GLX_ALPHA_SIZE = 11; |
| constexpr uint32_t GLX_DEPTH_SIZE = 12; |
| constexpr uint32_t GLX_STENCIL_SIZE = 13; |
| constexpr uint32_t GLX_ACCUM_RED_SIZE = 14; |
| constexpr uint32_t GLX_ACCUM_GREEN_SIZE = 15; |
| constexpr uint32_t GLX_ACCUM_BLUE_SIZE = 16; |
| constexpr uint32_t GLX_ACCUM_ALPHA_SIZE = 17; |
| |
| constexpr uint32_t GLX_CONFIG_CAVEAT = 0x20; |
| constexpr uint32_t GLX_VISUAL_CAVEAT_EXT = 0x20; |
| constexpr uint32_t GLX_X_VISUAL_TYPE = 0x22; |
| |
| constexpr uint32_t GLX_BIND_TO_TEXTURE_TARGETS_EXT = 0x20D3; |
| |
| constexpr uint32_t GLX_NONE = 0x8000; |
| constexpr uint32_t GLX_NONE_EXT = 0x8000; |
| constexpr uint32_t GLX_TRUE_COLOR = 0x8002; |
| constexpr uint32_t GLX_VISUAL_ID = 0x800B; |
| constexpr uint32_t GLX_DRAWABLE_TYPE = 0x8010; |
| constexpr uint32_t GLX_RENDER_TYPE = 0x8011; |
| constexpr uint32_t GLX_FBCONFIG_ID = 0x8013; |
| |
| constexpr uint32_t GLX_PIXMAP_BIT = 0x00000002; |
| constexpr uint32_t GLX_TEXTURE_2D_BIT_EXT = 0x00000002; |
| |
| constexpr uint32_t GLX_SAMPLE_BUFFERS_ARB = 100000; |
| constexpr uint32_t GLX_SAMPLES = 100001; |
| |
| constexpr uint32_t GL_FALSE = 0; |
| |
| namespace ui { |
| |
| namespace { |
| |
| bool IsArgbVisual(const x11::Connection::VisualInfo& visual) { |
| auto bits = [](auto x) { |
| return std::bitset<8 * sizeof(decltype(x))>(x).count(); |
| }; |
| auto bits_rgb = bits(visual.visual_type->red_mask) + |
| bits(visual.visual_type->green_mask) + |
| bits(visual.visual_type->blue_mask); |
| return static_cast<std::size_t>(visual.format->depth) > bits_rgb; |
| } |
| |
| // Used to filter visuals by the best class. |
| int VisualScore(x11::VisualClass c_class) { |
| // A higher score is more preferable. |
| switch (c_class) { |
| case x11::VisualClass::TrueColor: |
| return 1; |
| case x11::VisualClass::DirectColor: |
| return 0; |
| default: |
| return -1; |
| } |
| } |
| |
| } // anonymous namespace |
| |
| // static |
| VisualPickerGlx* VisualPickerGlx::GetInstance() { |
| return base::Singleton<VisualPickerGlx>::get(); |
| } |
| |
| x11::Glx::FbConfig VisualPickerGlx::GetFbConfigForFormat( |
| gfx::BufferFormat format) { |
| if (!config_map_) |
| FillConfigMap(); |
| auto it = config_map_->find(format); |
| return it == config_map_->end() ? x11::Glx::FbConfig{} : it->second; |
| } |
| |
| x11::VisualId VisualPickerGlx::PickBestGlVisual( |
| const x11::Glx::GetVisualConfigsReply& configs, |
| base::RepeatingCallback<bool(const x11::Connection::VisualInfo&)> pred, |
| bool want_alpha) const { |
| int highest_score = -1; |
| x11::VisualId best_visual{}; |
| for (size_t cfg = 0; cfg < configs.num_visuals; cfg++) { |
| size_t i = cfg * configs.num_properties; |
| std::map<uint32_t, uint32_t> props; |
| static constexpr uint32_t static_props[] = { |
| GLX_VISUAL_ID, GLX_X_VISUAL_TYPE, GLX_RENDER_TYPE, |
| GLX_RED_SIZE, GLX_GREEN_SIZE, GLX_BLUE_SIZE, |
| GLX_ALPHA_SIZE, GLX_ACCUM_RED_SIZE, GLX_ACCUM_GREEN_SIZE, |
| GLX_ACCUM_BLUE_SIZE, GLX_ACCUM_ALPHA_SIZE, GLX_DOUBLEBUFFER, |
| GLX_STEREO, GLX_BUFFER_SIZE, GLX_DEPTH_SIZE, |
| GLX_STENCIL_SIZE, GLX_AUX_BUFFERS, GLX_LEVEL, |
| }; |
| for (const uint32_t prop : static_props) |
| props[prop] = configs.property_list[i++]; |
| const size_t extra_props = |
| (configs.num_properties - std::size(static_props)) / 2; |
| for (size_t j = 0; j < extra_props; j++) { |
| const auto key = configs.property_list[i++]; |
| const auto value = configs.property_list[i++]; |
| // Mesa adds a (0, 0) key-value pair at the end of each property list. |
| if (!key) |
| continue; |
| props[key] = value; |
| } |
| |
| auto get = [&](uint32_t key) { |
| auto it = props.find(key); |
| return it == props.end() ? 0 : it->second; |
| }; |
| |
| const auto visual_id = static_cast<x11::VisualId>(get(GLX_VISUAL_ID)); |
| const auto* info = connection_->GetVisualInfoFromId(visual_id); |
| if (!pred.Run(*info)) |
| continue; |
| |
| if (!get(GLX_DOUBLEBUFFER) || get(GLX_STEREO)) |
| continue; |
| |
| auto caveat = get(GLX_VISUAL_CAVEAT_EXT); |
| if (caveat && caveat != GLX_NONE_EXT) |
| continue; |
| |
| // Give precedence to the root visual if it satisfies the basic |
| // requirements above. This can avoid an expensive copy-on-present. |
| if (visual_id == connection_->default_root_visual().visual_id) |
| return visual_id; |
| |
| int score = 0; |
| if (get(GLX_SAMPLE_BUFFERS_ARB) == 0) { |
| score++; |
| if (get(GLX_DEPTH_SIZE) == 0 && get(GLX_STENCIL_SIZE) == 0) { |
| score++; |
| const bool has_alpha = get(GLX_ALPHA_SIZE) > 0; |
| if (has_alpha == want_alpha) |
| score++; |
| } |
| } |
| |
| if (score > highest_score) { |
| highest_score = score; |
| best_visual = visual_id; |
| } |
| } |
| |
| return best_visual; |
| } |
| |
| x11::VisualId VisualPickerGlx::PickBestSystemVisual( |
| const x11::Glx::GetVisualConfigsReply& configs) const { |
| x11::Connection::VisualInfo default_visual_info = |
| *connection_->GetVisualInfoFromId( |
| connection_->default_root_visual().visual_id); |
| |
| auto is_compatible_with_root_visual = |
| [](const x11::Connection::VisualInfo& default_visual_info, |
| const x11::Connection::VisualInfo& visual_info) { |
| const auto& dvt = *default_visual_info.visual_type; |
| const auto& vt = *visual_info.visual_type; |
| return vt.c_class == dvt.c_class && |
| visual_info.format->depth == default_visual_info.format->depth && |
| vt.red_mask == dvt.red_mask && vt.green_mask == dvt.green_mask && |
| vt.blue_mask == dvt.blue_mask && |
| vt.colormap_entries == dvt.colormap_entries && |
| vt.bits_per_rgb_value == dvt.bits_per_rgb_value; |
| }; |
| |
| return PickBestGlVisual( |
| configs, |
| base::BindRepeating(is_compatible_with_root_visual, default_visual_info), |
| IsArgbVisual(default_visual_info)); |
| } |
| |
| x11::VisualId VisualPickerGlx::PickBestRgbaVisual( |
| const x11::Glx::GetVisualConfigsReply& configs) const { |
| int best_class_score = -1; |
| for (const auto& depth : connection_->default_screen().allowed_depths) { |
| for (const auto& vis : depth.visuals) |
| best_class_score = std::max(best_class_score, VisualScore(vis.c_class)); |
| } |
| auto pred = [](int best_class_score, |
| const x11::Connection::VisualInfo& visual_info) { |
| if (!IsArgbVisual(visual_info)) |
| return false; |
| return VisualScore(visual_info.visual_type->c_class) == best_class_score; |
| }; |
| return PickBestGlVisual(configs, base::BindRepeating(pred, best_class_score), |
| true); |
| } |
| |
| void VisualPickerGlx::FillConfigMap() { |
| DCHECK(!config_map_); |
| config_map_ = |
| std::make_unique<base::flat_map<gfx::BufferFormat, x11::Glx::FbConfig>>(); |
| |
| if (auto configs = connection_->glx() |
| .GetFBConfigs({static_cast<uint32_t>( |
| connection_->DefaultScreenId())}) |
| .Sync()) { |
| const auto n_cfgs = configs->num_FB_configs; |
| const auto n_props = configs->num_properties; |
| |
| // Iterate from back to front since "preferred" FB configs appear earlier. |
| for (size_t cfg = n_cfgs; cfg-- > 0;) { |
| std::map<uint32_t, uint32_t> props; |
| for (size_t prop = 0; prop < n_props; prop++) { |
| size_t i = 2 * cfg * n_props + 2 * prop; |
| const auto key = configs->property_list[i]; |
| const auto value = configs->property_list[i + 1]; |
| props[key] = value; |
| } |
| |
| auto get = [&](uint32_t key) { |
| auto it = props.find(key); |
| return it == props.end() ? 0 : it->second; |
| }; |
| |
| // Each config must have an ID. |
| auto id = get(GLX_FBCONFIG_ID); |
| DCHECK(id); |
| auto fbconfig = static_cast<x11::Glx::FbConfig>(id); |
| |
| // Ensure the config is compatible with pixmap drawing. |
| if (!(get(GLX_DRAWABLE_TYPE) & GLX_PIXMAP_BIT)) |
| continue; |
| |
| // Ensure we can bind to GL_TEXTURE_2D. |
| if (!(get(GLX_BIND_TO_TEXTURE_TARGETS_EXT) & GLX_TEXTURE_2D_BIT_EXT)) |
| continue; |
| |
| // No double-buffering. |
| if (get(GLX_DOUBLEBUFFER) != GL_FALSE) |
| continue; |
| |
| // Prefer true-color over direct-color. |
| if (get(GLX_X_VISUAL_TYPE) != GLX_TRUE_COLOR) |
| continue; |
| |
| // No caveats. |
| auto caveat = get(GLX_CONFIG_CAVEAT); |
| if (caveat && caveat != GLX_NONE) |
| continue; |
| |
| // No antialiasing needed. |
| if (get(GLX_SAMPLES)) |
| continue; |
| |
| // No depth buffer needed. |
| if (get(GLX_DEPTH_SIZE)) |
| continue; |
| |
| auto r = get(GLX_RED_SIZE); |
| auto g = get(GLX_GREEN_SIZE); |
| auto b = get(GLX_BLUE_SIZE); |
| auto a = get(GLX_ALPHA_SIZE); |
| if (r == 5 && g == 6 && b == 5 && a == 0) |
| (*config_map_)[gfx::BufferFormat::BGR_565] = fbconfig; |
| else if (r == 8 && g == 8 && b == 8 && a == 0) |
| (*config_map_)[gfx::BufferFormat::BGRX_8888] = fbconfig; |
| else if (r == 10 && g == 10 && b == 10 && a == 0) |
| (*config_map_)[gfx::BufferFormat::BGRA_1010102] = fbconfig; |
| else if (r == 8 && g == 8 && b == 8 && a == 8) |
| (*config_map_)[gfx::BufferFormat::BGRA_8888] = fbconfig; |
| } |
| } |
| } |
| |
| VisualPickerGlx::VisualPickerGlx() : connection_(x11::Connection::Get()) { |
| auto configs = |
| connection_->glx() |
| .GetVisualConfigs( |
| {static_cast<uint32_t>(connection_->DefaultScreenId())}) |
| .Sync(); |
| |
| if (configs) { |
| system_visual_ = PickBestSystemVisual(*configs.reply); |
| rgba_visual_ = PickBestRgbaVisual(*configs.reply); |
| } |
| } |
| |
| VisualPickerGlx::~VisualPickerGlx() = default; |
| |
| } // namespace ui |