Default back to linear scaling and add config options to set

This commit is contained in:
Ottatop 2024-03-29 11:57:35 -05:00
parent 7496ebd697
commit d52192a2ba
15 changed files with 360 additions and 63 deletions

View file

@ -92,16 +92,24 @@ require("pinnacle").setup(function(Pinnacle)
end
end
Input.keybind({ mod_key }, "=", function()
Input.keybind({ mod_key, "shift" }, "=", function()
Output.get_focused():increase_scale(0.25)
layout_outputs_in_line()
end)
Input.keybind({ mod_key }, "-", function()
Input.keybind({ mod_key, "shift" }, "-", function()
Output.get_focused():decrease_scale(0.25)
layout_outputs_in_line()
end)
Input.keybind({ mod_key }, "u", function()
Pinnacle.render.set_upscale_filter("nearest_neighbor")
end)
Input.keybind({ mod_key }, "d", function()
Pinnacle.render.set_upscale_filter("bilinear")
end)
--------------------
-- Tags --
--------------------

View file

@ -28,5 +28,6 @@ build = {
["pinnacle.util"] = "pinnacle/util.lua",
["pinnacle.signal"] = "pinnacle/signal.lua",
["pinnacle.layout"] = "pinnacle/layout.lua",
["pinnacle.render"] = "pinnacle/render.lua",
},
}

View file

@ -6,7 +6,7 @@ local client = require("pinnacle.grpc.client")
---The entry point to configuration.
---
---This module contains one function: `setup`, which is how you'll access all the ways to configure Pinnacle.
---This module contains the `setup` function, which is how you'll access all the ways to configure Pinnacle.
---@class Pinnacle
local pinnacle = {
---@type Input
@ -23,6 +23,8 @@ local pinnacle = {
util = require("pinnacle.util"),
---@type Layout
layout = require("pinnacle.layout"),
---@type Render
render = require("pinnacle.render"),
}
---Quit Pinnacle.
@ -50,6 +52,8 @@ end
function pinnacle.setup(config_fn)
require("pinnacle.grpc.protobuf").build_protos()
-- This function ensures a config won't run forever if Pinnacle is killed
-- and doesn't kill configs on drop.
client.loop:wrap(function()
while true do
require("cqueues").sleep(60)

View file

@ -19,6 +19,7 @@ function protobuf.build_protos()
PINNACLE_PROTO_DIR .. "/pinnacle/window/" .. version .. "/window.proto",
PINNACLE_PROTO_DIR .. "/pinnacle/signal/" .. version .. "/signal.proto",
PINNACLE_PROTO_DIR .. "/pinnacle/layout/" .. version .. "/layout.proto",
PINNACLE_PROTO_DIR .. "/pinnacle/render/" .. version .. "/render.proto",
PINNACLE_PROTO_DIR .. "/google/protobuf/empty.proto",
}

View file

@ -0,0 +1,69 @@
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
local client = require("pinnacle.grpc.client")
---The protobuf absolute path prefix
local prefix = "pinnacle.render." .. client.version .. "."
local service = prefix .. "RenderService"
---@type table<string, { request_type: string?, response_type: string? }>
---@enum (key) RenderServiceMethod
local rpc_types = {
SetUpscaleFilter = {},
SetDownscaleFilter = {},
}
---Build GrpcRequestParams
---@param method RenderServiceMethod
---@param data table
---@return GrpcRequestParams
local function build_grpc_request_params(method, data)
local req_type = rpc_types[method].request_type
local resp_type = rpc_types[method].response_type
---@type GrpcRequestParams
return {
service = service,
method = method,
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
response_type = resp_type and prefix .. resp_type,
data = data,
}
end
---Rendering management.
---
---@class Render
local render = {}
---@alias ScalingFilter
---| "bilinear"
---| "nearest_neighbor"
---@type table<ScalingFilter, integer>
local filter_name_to_filter_value = {
bilinear = 1,
nearest_neighbor = 2,
}
---Set the upscale filter the renderer will use to upscale buffers.
---
---@param filter ScalingFilter
function render.set_upscale_filter(filter)
client.unary_request(
build_grpc_request_params("SetUpscaleFilter", { filter = filter_name_to_filter_value[filter] })
)
end
---Set the downscale filter the renderer will use to downscale buffers.
---
---@param filter ScalingFilter
function render.set_downscale_filter(filter)
client.unary_request(
build_grpc_request_params("SetDownscaleFilter", { filter = filter_name_to_filter_value[filter] })
)
end
return render

View file

@ -0,0 +1,35 @@
syntax = "proto2";
package pinnacle.render.v0alpha1;
import "google/protobuf/empty.proto";
// The filtering method.
enum Filter {
FILTER_UNSPECIFIED = 0;
// Bilinear filtering.
//
// This will cause up- and downscaling to be blurry.
FILTER_BILINEAR = 1;
// Nearest neighbor filtering.
//
// This will cause edges to become pixelated when scaling.
FILTER_NEAREST_NEIGHBOR = 2;
}
message SetUpscaleFilterRequest {
// The filter that will be used.
optional Filter filter = 1;
}
message SetDownscaleFilterRequest {
// The filter that will be used.
optional Filter filter = 1;
}
service RenderService {
// Set the upscaling filter the renderer will use when upscaling buffers.
rpc SetUpscaleFilter(SetUpscaleFilterRequest) returns (google.protobuf.Empty);
// Set the downscaling filter the renderer will use when downscaling buffers.
rpc SetDownscaleFilter(SetDownscaleFilterRequest) returns (google.protobuf.Empty);
}

View file

@ -88,6 +88,7 @@ use layout::Layout;
use output::Output;
use pinnacle::Pinnacle;
use process::Process;
use render::Render;
use signal::SignalState;
use tag::Tag;
use tokio::sync::{
@ -104,6 +105,7 @@ pub mod layout;
pub mod output;
pub mod pinnacle;
pub mod process;
pub mod render;
pub mod signal;
pub mod tag;
pub mod util;
@ -121,6 +123,7 @@ static OUTPUT: OnceLock<Output> = OnceLock::new();
static TAG: OnceLock<Tag> = OnceLock::new();
static SIGNAL: OnceLock<RwLock<SignalState>> = OnceLock::new();
static LAYOUT: OnceLock<Layout> = OnceLock::new();
static RENDER: OnceLock<Render> = OnceLock::new();
/// A struct containing static references to all of the configuration structs.
#[derive(Debug, Clone, Copy)]
@ -139,6 +142,8 @@ pub struct ApiModules {
pub tag: &'static Tag,
/// The [`Layout`] struct
pub layout: &'static Layout,
/// The [`Render`] struct
pub render: &'static Render,
}
/// Connects to Pinnacle and builds the configuration structs.
@ -166,6 +171,7 @@ pub async fn connect(
let tag = TAG.get_or_init(|| Tag::new(channel.clone()));
let output = OUTPUT.get_or_init(|| Output::new(channel.clone()));
let layout = LAYOUT.get_or_init(|| Layout::new(channel.clone()));
let render = RENDER.get_or_init(|| Render::new(channel.clone()));
SIGNAL
.set(RwLock::new(SignalState::new(
@ -182,6 +188,7 @@ pub async fn connect(
output,
tag,
layout,
render,
};
Ok((modules, fut_recv))

52
api/rust/src/render.rs Normal file
View file

@ -0,0 +1,52 @@
//! Rendering management.
use pinnacle_api_defs::pinnacle::render::v0alpha1::{
render_service_client::RenderServiceClient, SetDownscaleFilterRequest, SetUpscaleFilterRequest,
};
use tonic::transport::Channel;
use crate::block_on_tokio;
/// A struct that allows you to manage rendering.
#[derive(Debug, Clone)]
pub struct Render {
client: RenderServiceClient<Channel>,
}
/// What filter to use when scaling.
pub enum ScalingFilter {
/// Use a bilinear filter.
///
/// This will make up- and downscaling blurry.
Bilinear,
/// Use a nearest neighbor filter.
///
/// This will cause scaling to look pixelated.
NearestNeighbor,
}
impl Render {
pub(crate) fn new(channel: Channel) -> Self {
Self {
client: RenderServiceClient::new(channel),
}
}
/// Set the upscaling filter that will be used for rendering.
pub fn set_upscale_filter(&self, filter: ScalingFilter) {
let mut client = self.client.clone();
block_on_tokio(client.set_upscale_filter(SetUpscaleFilterRequest {
filter: Some(filter as i32),
}))
.unwrap();
}
/// Set the downscaling filter that will be used for rendering.
pub fn set_downscale_filter(&self, filter: ScalingFilter) {
let mut client = self.client.clone();
block_on_tokio(client.set_downscale_filter(SetDownscaleFilterRequest {
filter: Some(filter as i32),
}))
.unwrap();
}
}

View file

@ -15,6 +15,7 @@ fn main() {
formatcp!("../api/protocol/pinnacle/window/{VERSION}/window.proto"),
formatcp!("../api/protocol/pinnacle/signal/{VERSION}/signal.proto"),
formatcp!("../api/protocol/pinnacle/layout/{VERSION}/layout.proto"),
formatcp!("../api/protocol/pinnacle/render/{VERSION}/render.proto"),
];
let descriptor_path = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("pinnacle.bin");

View file

@ -74,6 +74,12 @@ pub mod pinnacle {
tonic::include_proto!("pinnacle.layout.v0alpha1");
}
}
pub mod render {
pub mod v0alpha1 {
tonic::include_proto!("pinnacle.render.v0alpha1");
}
}
}
pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("pinnacle");

View file

@ -20,6 +20,9 @@ use pinnacle_api_defs::pinnacle::{
},
},
process::v0alpha1::{process_service_server, SetEnvRequest, SpawnRequest, SpawnResponse},
render::v0alpha1::{
render_service_server, Filter, SetDownscaleFilterRequest, SetUpscaleFilterRequest,
},
tag::{
self,
v0alpha1::{
@ -30,6 +33,7 @@ use pinnacle_api_defs::pinnacle::{
v0alpha1::{pinnacle_service_server, PingRequest, PingResponse, QuitRequest, SetOrToggle},
};
use smithay::{
backend::renderer::TextureFilter,
desktop::layer_map_for_output,
input::keyboard::XkbConfig,
output::Scale,
@ -46,6 +50,7 @@ use tonic::{Request, Response, Status, Streaming};
use tracing::{debug, error, warn};
use crate::{
backend::BackendData,
config::ConnectorSavedState,
input::ModifierMask,
output::OutputName,
@ -1135,3 +1140,66 @@ impl output_service_server::OutputService for OutputService {
.await
}
}
pub struct RenderService {
sender: StateFnSender,
}
impl RenderService {
pub fn new(sender: StateFnSender) -> Self {
Self { sender }
}
}
#[tonic::async_trait]
impl render_service_server::RenderService for RenderService {
async fn set_upscale_filter(
&self,
request: Request<SetUpscaleFilterRequest>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
if let Filter::Unspecified = request.filter() {
return Err(Status::invalid_argument("unspecified filter"));
}
let filter = match request.filter() {
Filter::Bilinear => TextureFilter::Linear,
Filter::NearestNeighbor => TextureFilter::Nearest,
_ => unreachable!(),
};
run_unary_no_response(&self.sender, move |state| {
state.backend.set_upscale_filter(filter);
for output in state.space.outputs().cloned().collect::<Vec<_>>() {
state.backend.reset_buffers(&output);
state.schedule_render(&output);
}
})
.await
}
async fn set_downscale_filter(
&self,
request: Request<SetDownscaleFilterRequest>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
if let Filter::Unspecified = request.filter() {
return Err(Status::invalid_argument("unspecified filter"));
}
let filter = match request.filter() {
Filter::Bilinear => TextureFilter::Linear,
Filter::NearestNeighbor => TextureFilter::Nearest,
_ => unreachable!(),
};
run_unary_no_response(&self.sender, move |state| {
state.backend.set_downscale_filter(filter);
for output in state.space.outputs().cloned().collect::<Vec<_>>() {
state.backend.reset_buffers(&output);
state.schedule_render(&output);
}
})
.await
}
}

View file

@ -10,7 +10,7 @@ use smithay::{
default_primary_scanout_output_compare, utils::select_dmabuf_feedback,
RenderElementStates,
},
ImportDma,
ImportDma, Renderer, TextureFilter,
},
},
delegate_dmabuf,
@ -26,6 +26,7 @@ use smithay::{
fractional_scale::with_fractional_scale,
},
};
use tracing::error;
use crate::{
state::{State, SurfaceDmabufFeedback},
@ -51,6 +52,32 @@ pub enum Backend {
}
impl Backend {
pub fn set_upscale_filter(&mut self, filter: TextureFilter) {
match self {
Backend::Winit(winit) => {
if let Err(err) = winit.backend.renderer().upscale_filter(filter) {
error!("Failed to set winit upscale filter: {err}");
}
}
Backend::Udev(udev) => udev.upscale_filter = filter,
#[cfg(feature = "testing")]
Backend::Dummy(_) => (),
}
}
pub fn set_downscale_filter(&mut self, filter: TextureFilter) {
match self {
Backend::Winit(winit) => {
if let Err(err) = winit.backend.renderer().downscale_filter(filter) {
error!("Failed to set winit upscale filter: {err}");
}
}
Backend::Udev(udev) => udev.downscale_filter = filter,
#[cfg(feature = "testing")]
Backend::Dummy(_) => (),
}
}
pub fn seat_name(&self) -> String {
match self {
Backend::Winit(winit) => winit.seat_name(),
@ -86,7 +113,6 @@ impl Backend {
}
}
/// A trait defining common methods for each available backend: winit and tty-udev
pub trait BackendData: 'static {
fn seat_name(&self) -> String;
fn reset_buffers(&mut self, output: &Output);
@ -95,6 +121,35 @@ pub trait BackendData: 'static {
fn early_import(&mut self, surface: &WlSurface);
}
impl BackendData for Backend {
fn seat_name(&self) -> String {
match self {
Backend::Winit(winit) => winit.seat_name(),
Backend::Udev(udev) => udev.seat_name(),
#[cfg(feature = "testing")]
Backend::Dummy(dummy) => dummy.seat_name(),
}
}
fn reset_buffers(&mut self, output: &Output) {
match self {
Backend::Winit(winit) => winit.reset_buffers(output),
Backend::Udev(udev) => udev.reset_buffers(output),
#[cfg(feature = "testing")]
Backend::Dummy(dummy) => dummy.reset_buffers(output),
}
}
fn early_import(&mut self, surface: &WlSurface) {
match self {
Backend::Winit(winit) => winit.early_import(surface),
Backend::Udev(udev) => udev.early_import(surface),
#[cfg(feature = "testing")]
Backend::Dummy(dummy) => dummy.early_import(surface),
}
}
}
/// Update surface primary scanout outputs and send frames and dmabuf feedback to visible windows
/// and layers.
pub fn post_repaint(

View file

@ -29,9 +29,9 @@ use smithay::{
renderer::{
damage,
element::{texture::TextureBuffer, RenderElement, RenderElementStates},
gles::{GlesRenderer, GlesTexture},
gles::GlesRenderer,
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer, MultiTexture},
Bind, ExportMem, ImportDma, ImportEgl, ImportMemWl, Offscreen, Renderer, TextureFilter,
Bind, ImportDma, ImportEgl, ImportMemWl, Renderer, TextureFilter,
},
session::{
self,
@ -52,10 +52,7 @@ use smithay::{
reexports::{
ash::vk::ExtPhysicalDeviceDrmFn,
calloop::{EventLoop, Idle, LoopHandle, RegistrationToken},
drm::{
control::{connector, crtc, ModeTypeFlags},
Device,
},
drm::control::{connector, crtc, ModeTypeFlags},
input::Libinput,
rustix::fs::OFlags,
wayland_protocols::wp::{
@ -122,6 +119,9 @@ pub struct Udev {
pointer_images: Vec<(xcursor::parser::Image, TextureBuffer<MultiTexture>)>,
pointer_element: PointerElement<MultiTexture>,
pointer_image: crate::cursor::Cursor,
pub(super) upscale_filter: TextureFilter,
pub(super) downscale_filter: TextureFilter,
}
impl Backend {
@ -320,6 +320,9 @@ pub fn setup_udev(
pointer_image: crate::cursor::Cursor::load(),
pointer_images: Vec::new(),
pointer_element: PointerElement::default(),
upscale_filter: TextureFilter::Linear,
downscale_filter: TextureFilter::Linear,
};
let display_handle = display.handle();
@ -721,14 +724,14 @@ struct SurfaceCompositorRenderResult {
/// Render a frame with the given elements.
///
/// This frame needs to be queued for scanout afterwards.
fn render_frame<R, E, Target>(
fn render_frame<R, E>(
compositor: &mut GbmDrmCompositor,
renderer: &mut R,
elements: &[E],
clear_color: [f32; 4],
) -> Result<SurfaceCompositorRenderResult, SwapBuffersError>
where
R: Renderer + Bind<Dmabuf> + Bind<Target> + Offscreen<Target> + ExportMem,
R: Renderer + Bind<Dmabuf>,
<R as Renderer>::TextureId: 'static,
<R as Renderer>::Error: Into<SwapBuffersError>,
E: RenderElement<R>,
@ -942,30 +945,12 @@ impl State {
};
let compositor = {
let driver = match device.drm.get_driver() {
Ok(driver) => driver,
Err(err) => {
warn!("Failed to query drm driver: {}", err);
return;
}
};
let mut planes = surface.planes().clone();
// Using an overlay plane on a nvidia card breaks
if driver
.name()
.to_string_lossy()
.to_lowercase()
.contains("nvidia")
|| driver
.description()
.to_string_lossy()
.to_lowercase()
.contains("nvidia")
{
planes.overlay = vec![];
}
// INFO: We are disabling overlay planes because it seems that any elements on
// | overlay planes don't get up/downscaled according to the set filter;
// | it always defaults to linear.
planes.overlay.clear();
match DrmCompositor::new(
&output,
@ -1238,9 +1223,11 @@ impl State {
assert!(matches!(surface.render_state, RenderState::Scheduled(_)));
// TODO get scale from the rendersurface when supporting HiDPI
let frame = udev
.pointer_image
.get_image(1 /*scale*/, self.clock.now().into());
let frame = udev.pointer_image.get_image(
1,
// output.current_scale().integer_scale() as u32,
self.clock.now().into(),
);
let render_node = surface.render_node;
let primary_gpu = udev.primary_gpu;
@ -1253,9 +1240,8 @@ impl State {
}
.expect("failed to create MultiRenderer");
// TODO: set from config
let _ = renderer.upscale_filter(TextureFilter::Nearest);
let _ = renderer.downscale_filter(TextureFilter::Nearest);
let _ = renderer.upscale_filter(udev.upscale_filter);
let _ = renderer.downscale_filter(udev.downscale_filter);
let pointer_images = &mut udev.pointer_images;
let pointer_image = pointer_images
@ -1360,7 +1346,7 @@ fn render_surface(
Some(pointer_image),
);
let res = render_frame::<_, _, GlesTexture>(
let res = render_frame(
&mut surface.compositor,
renderer,
&output_render_elements,

View file

@ -8,13 +8,13 @@ use smithay::{
renderer::{
damage::{self, OutputDamageTracker},
gles::{GlesRenderer, GlesTexture},
ImportDma, ImportEgl, ImportMemWl, Renderer, TextureFilter,
ImportDma, ImportEgl, ImportMemWl,
},
winit::{self, WinitEvent, WinitGraphicsBackend},
},
desktop::utils::send_frames_surface_tree,
desktop::{layer_map_for_output, utils::send_frames_surface_tree},
input::pointer::CursorImageStatus,
output::{Output, Subpixel},
output::{Output, Scale, Subpixel},
reexports::{
calloop::{
timer::{TimeoutAction, Timer},
@ -30,6 +30,7 @@ use smithay::{
utils::{IsAlive, Transform},
wayland::dmabuf::{DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufState},
};
use tracing::error;
use crate::{
render::{pointer::PointerElement, take_presentation_feedback},
@ -88,14 +89,6 @@ pub fn setup_winit(
Err(err) => anyhow::bail!("Failed to init winit backend: {err}"),
};
// TODO: set from config
winit_backend
.renderer()
.upscale_filter(TextureFilter::Nearest)?;
winit_backend
.renderer()
.downscale_filter(TextureFilter::Nearest)?;
let mode = smithay::output::Mode {
size: winit_backend.window_size(),
refresh: 144_000,
@ -206,7 +199,7 @@ pub fn setup_winit(
true,
|_| {},
) {
tracing::error!("Failed to start XWayland: {err}");
error!("Failed to start XWayland: {err}");
}
let insert_ret =
@ -214,17 +207,25 @@ pub fn setup_winit(
.loop_handle
.insert_source(Timer::immediate(), move |_instant, _metadata, state| {
let status = winit_evt_loop.dispatch_new_events(|event| match event {
WinitEvent::Resized {
size,
scale_factor: _,
} => {
WinitEvent::Resized { size, scale_factor } => {
let mode = smithay::output::Mode {
size,
refresh: 144_000,
};
state.resize_output(&output, mode);
output.change_current_state(
Some(mode),
None,
Some(Scale::Fractional(scale_factor)),
None,
);
layer_map_for_output(&output).arrange();
state.request_layout(&output);
}
WinitEvent::Focus(focused) => {
if focused {
state.backend.winit_mut().reset_buffers(&output);
}
}
WinitEvent::Focus(_) => {}
WinitEvent::Input(input_evt) => {
state.process_input_event(input_evt);
}
@ -315,7 +316,7 @@ impl State {
let has_rendered = render_output_result.damage.is_some();
if let Some(damage) = render_output_result.damage {
if let Err(err) = winit.backend.submit(Some(&damage)) {
tracing::warn!("{}", err);
error!("{}", err);
}
}

View file

@ -1,7 +1,7 @@
use crate::{
api::{
layout::LayoutService, signal::SignalService, window::WindowService, InputService,
OutputService, PinnacleService, ProcessService, TagService,
OutputService, PinnacleService, ProcessService, RenderService, TagService,
},
input::ModifierMask,
output::OutputName,
@ -20,6 +20,7 @@ use pinnacle_api_defs::pinnacle::{
layout::v0alpha1::layout_service_server::LayoutServiceServer,
output::v0alpha1::output_service_server::OutputServiceServer,
process::v0alpha1::process_service_server::ProcessServiceServer,
render::v0alpha1::render_service_server::RenderServiceServer,
signal::v0alpha1::signal_service_server::SignalServiceServer,
tag::v0alpha1::tag_service_server::TagServiceServer,
v0alpha1::pinnacle_service_server::PinnacleServiceServer,
@ -479,6 +480,7 @@ impl State {
let window_service = WindowService::new(grpc_sender.clone());
let signal_service = SignalService::new(grpc_sender.clone());
let layout_service = LayoutService::new(grpc_sender.clone());
let render_service = RenderService::new(grpc_sender.clone());
let refl_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(pinnacle_api_defs::FILE_DESCRIPTOR_SET)
@ -498,7 +500,8 @@ impl State {
.add_service(OutputServiceServer::new(output_service))
.add_service(WindowServiceServer::new(window_service))
.add_service(SignalServiceServer::new(signal_service))
.add_service(LayoutServiceServer::new(layout_service));
.add_service(LayoutServiceServer::new(layout_service))
.add_service(RenderServiceServer::new(render_service));
match self.xdisplay.as_ref() {
Some(_) => {