1
0
Fork 0
forked from wry/wry

Merge pull request #384 from mahkoh/jorth/legacy-tex

vulkan: optimize draw calls
This commit is contained in:
mahkoh 2025-02-28 11:23:06 +01:00 committed by GitHub
commit 5d81d7609a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 1104 additions and 551 deletions

View file

@ -15,16 +15,22 @@ pub fn main() -> anyhow::Result<()> {
compile_simple("tex.frag")?;
compile_simple("out.vert")?;
compile_simple("out.frag")?;
compile_simple("legacy/fill.frag")?;
compile_simple("legacy/fill.vert")?;
compile_simple("legacy/tex.vert")?;
compile_simple("legacy/tex.frag")?;
Ok(())
}
fn compile_simple(name: &str) -> anyhow::Result<()> {
compile_shader(name, &format!("{name}.spv"), None).with_context(|| name.to_string())
let out = format!("{name}.spv").replace("/", "_");
compile_shader(name, &out).with_context(|| name.to_string())
}
fn compile_shader(name: &str, out: &str, options: Option<CompileOptions>) -> anyhow::Result<()> {
let read = |path: &str| std::fs::read_to_string(format!("{}/{}", ROOT, path));
let mut options = options.unwrap_or_else(|| CompileOptions::new().unwrap());
fn compile_shader(name: &str, out: &str) -> anyhow::Result<()> {
let root = Path::new(ROOT).join(Path::new(name).parent().unwrap());
let read = |path: &str| std::fs::read_to_string(root.join(path));
let mut options = CompileOptions::new().unwrap();
options.set_include_callback(|name, _, _, _| {
Ok(ResolvedInclude {
resolved_name: name.to_string(),
@ -40,7 +46,7 @@ fn compile_shader(name: &str, out: &str, options: Option<CompileOptions>) -> any
"vert" => shaderc::ShaderKind::Vertex,
n => bail!("Unknown shader stage {}", n),
};
let src = read(name)?;
let src = std::fs::read_to_string(format!("{}/{}", ROOT, name))?;
let compiler = shaderc::Compiler::new().unwrap();
let binary = compiler
.compile_into_spirv(&src, stage, name, "main", Some(&options))

View file

@ -1,6 +1,7 @@
use {
crate::{
cli::{GlobalArgs, color::parse_color, duration::parse_duration},
theme::TransferFunction,
tools::tool_client::{ToolClient, with_tool_client},
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
},
@ -85,12 +86,13 @@ impl DamageTracking {
}
DamageTrackingCmd::SetColor(c) => {
let color = parse_color(&c.color);
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
tc.send(SetVisualizerColor {
self_id: dt,
r: color.r,
g: color.g,
b: color.b,
a: color.a,
r,
g,
b,
a,
});
}
DamageTrackingCmd::SetDecay(c) => {

View file

@ -14,7 +14,7 @@ use {
output_schedule::map_cursor_hz,
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
theme::{Color, ThemeSized},
theme::{Color, ThemeSized, TransferFunction},
tree::{
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
TearingMode, VrrMode, WsMoveConfig, move_ws_to_output,
@ -1599,8 +1599,8 @@ impl ConfigProxyHandler {
fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> {
let color = self.get_color(colorable)?.get();
let color =
jay_config::theme::Color::new_f32_premultiplied(color.r, color.g, color.b, color.a);
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a);
self.respond(Response::GetColor { color });
Ok(())
}

View file

@ -2,6 +2,7 @@ use {
crate::{
async_engine::AsyncEngine,
fixed::Fixed,
gfx_api::GfxApiOpt,
ifs::wl_output::WlOutputGlobal,
rect::{Rect, Region},
renderer::renderer_base::RendererBase,
@ -93,7 +94,7 @@ impl DamageVisualizer {
entry_added: Default::default(),
enabled: Default::default(),
decay: Cell::new(Duration::from_secs(2)),
color: Cell::new(Color::from_rgba_straight(255, 0, 0, 128)),
color: Cell::new(Color::from_srgba_straight(255, 0, 0, 128)),
}
}
@ -160,6 +161,7 @@ impl DamageVisualizer {
let dx = -cursor_rect.x1();
let dy = -cursor_rect.y1();
let decay_millis = decay.as_millis() as u64 as f32;
renderer.ops.push(GfxApiOpt::Sync);
for entry in entries.iter().rev() {
let region = Region::new(entry.rect);
let region = region.subtract(&used);

View file

@ -352,25 +352,16 @@ impl dyn GfxFramebuffer {
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
) -> Result<Option<SyncFile>, GfxError> {
self.clear_with(acquire_sync, release_sync, 0.0, 0.0, 0.0, 0.0)
self.clear_with(acquire_sync, release_sync, &Color::TRANSPARENT)
}
pub fn clear_with(
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
r: f32,
g: f32,
b: f32,
a: f32,
color: &Color,
) -> Result<Option<SyncFile>, GfxError> {
self.render(
acquire_sync,
release_sync,
&[],
Some(&Color { r, g, b, a }),
None,
)
self.render(acquire_sync, release_sync, &[], Some(color), None)
}
pub fn logical_size(&self, transform: Transform) -> (i32, i32) {

View file

@ -84,7 +84,7 @@ use {
GL_TRIANGLE_STRIP, GL_TRIANGLES,
},
},
theme::Color,
theme::{Color, TransferFunction},
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
video::{
dmabuf::DMA_BUF_SYNC_READ,
@ -305,10 +305,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
}
fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let gles = ctx.ctx.dpy.gles;
unsafe {
(gles.glUseProgram)(ctx.fill_prog.prog);
(gles.glUniform4f)(ctx.fill_prog_color, color.r, color.g, color.b, color.a);
(gles.glUniform4f)(ctx.fill_prog_color, r, g, b, a);
(gles.glVertexAttribPointer)(
ctx.fill_prog_pos as _,
2,

View file

@ -18,7 +18,7 @@ use {
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
},
rect::Region,
theme::Color,
theme::{Color, TransferFunction},
},
std::{
cell::Cell,
@ -78,7 +78,8 @@ impl Framebuffer {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(gles.glViewport)(0, 0, self.gl.width, self.gl.height);
if let Some(c) = clear {
(gles.glClearColor)(c.r, c.g, c.b, c.a);
let [r, g, b, a] = c.to_array(TransferFunction::Srgb);
(gles.glClearColor)(r, g, b, a);
(gles.glClear)(GL_COLOR_BUFFER_BIT);
}
(gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

View file

@ -1,6 +1,7 @@
mod allocator;
mod blend_buffer;
mod bo_allocator;
mod buffer_cache;
mod command;
mod descriptor;
mod descriptor_buffer;
@ -17,6 +18,7 @@ mod shaders;
mod shm_image;
mod staging;
mod transfer;
mod transfer_functions;
use {
crate::{

View file

@ -0,0 +1,163 @@
use {
crate::{
gfx_apis::vulkan::{
VulkanError,
allocator::{VulkanAllocation, VulkanAllocator},
device::VulkanDevice,
},
utils::on_drop::OnDrop,
},
ash::vk::{
Buffer, BufferCreateInfo, BufferDeviceAddressInfo, BufferUsageFlags, DeviceAddress,
DeviceSize,
},
gpu_alloc::UsageFlags,
std::{cell::RefCell, mem::ManuallyDrop, rc::Rc},
};
pub struct VulkanBufferCache {
device: Rc<VulkanDevice>,
allocator: Rc<VulkanAllocator>,
buffers: RefCell<Vec<VulkanBufferUnused>>,
usage: BufferUsageFlags,
}
pub struct VulkanBuffer {
cache: Rc<VulkanBufferCache>,
pub buffer: ManuallyDrop<VulkanBufferUnused>,
}
pub struct VulkanBufferUnused {
device: Rc<VulkanDevice>,
pub size: DeviceSize,
pub buffer: Buffer,
pub allocation: VulkanAllocation,
pub address: DeviceAddress,
}
impl VulkanBufferCache {
pub fn new(
device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>,
usage: BufferUsageFlags,
) -> Rc<Self> {
Rc::new(Self {
device: device.clone(),
allocator: allocator.clone(),
buffers: Default::default(),
usage,
})
}
pub fn for_descriptor_buffer(
device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>,
has_sampler: bool,
) -> Rc<Self> {
let mut usage = BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT
| BufferUsageFlags::SHADER_DEVICE_ADDRESS;
if has_sampler {
usage |= BufferUsageFlags::SAMPLER_DESCRIPTOR_BUFFER_EXT;
}
Self::new(device, allocator, usage)
}
pub fn usage(&self) -> BufferUsageFlags {
self.usage
}
pub fn allocate(
self: &Rc<Self>,
capacity: DeviceSize,
align: DeviceSize,
) -> Result<VulkanBuffer, VulkanError> {
const MIN_ALLOCATION: DeviceSize = 1024;
let capacity = capacity.max(MIN_ALLOCATION);
let mut smallest = None;
let mut smallest_size = DeviceSize::MAX;
let mut fitting = None;
let mut fitting_size = DeviceSize::MAX;
let buffers = &mut *self.buffers.borrow_mut();
for (idx, buffer) in buffers.iter().enumerate() {
if buffer.size >= capacity {
if buffer.size < fitting_size {
fitting = Some(idx);
fitting_size = buffer.size;
}
} else {
if buffer.size < smallest_size {
smallest = Some(idx);
smallest_size = buffer.size;
}
}
}
if let Some(idx) = fitting {
return Ok(VulkanBuffer {
cache: self.clone(),
buffer: ManuallyDrop::new(buffers.swap_remove(idx)),
});
}
if let Some(idx) = smallest {
log::debug!("discarding size {}", smallest_size);
buffers.swap_remove(idx);
}
let size = capacity.checked_next_power_of_two().unwrap();
log::debug!("allocating size {}", size);
let buffer = {
let info = BufferCreateInfo::default().size(size).usage(self.usage);
unsafe {
self.device
.device
.create_buffer(&info, None)
.map_err(VulkanError::CreateBuffer)?
}
};
let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) });
let mut memory_requirements =
unsafe { self.device.device.get_buffer_memory_requirements(buffer) };
memory_requirements.alignment = memory_requirements.alignment.max(align);
let allocation = {
let flags = UsageFlags::UPLOAD
| UsageFlags::FAST_DEVICE_ACCESS
| UsageFlags::HOST_ACCESS
| UsageFlags::DEVICE_ADDRESS;
self.allocator.alloc(&memory_requirements, flags, true)?
};
unsafe {
self.device
.device
.bind_buffer_memory(buffer, allocation.memory, allocation.offset)
.map_err(VulkanError::BindBufferMemory)?;
}
destroy_buffer.forget();
let address = {
let info = BufferDeviceAddressInfo::default().buffer(buffer);
unsafe { self.device.device.get_buffer_device_address(&info) }
};
Ok(VulkanBuffer {
cache: self.clone(),
buffer: ManuallyDrop::new(VulkanBufferUnused {
device: self.device.clone(),
size,
buffer,
allocation,
address,
}),
})
}
}
impl Drop for VulkanBuffer {
fn drop(&mut self) {
let buffer = unsafe { ManuallyDrop::take(&mut self.buffer) };
self.cache.buffers.borrow_mut().push(buffer);
}
}
impl Drop for VulkanBufferUnused {
fn drop(&mut self) {
unsafe {
self.device.device.destroy_buffer(self.buffer, None);
}
}
}

View file

@ -1,41 +1,8 @@
use {
crate::{
gfx_apis::vulkan::{
VulkanError,
allocator::{VulkanAllocation, VulkanAllocator},
descriptor::VulkanDescriptorSetLayout,
device::VulkanDevice,
},
utils::on_drop::OnDrop,
},
ash::vk::{
Buffer, BufferCreateInfo, BufferDeviceAddressInfo, BufferUsageFlags, DeviceAddress,
DeviceSize,
},
gpu_alloc::UsageFlags,
std::{cell::RefCell, mem::ManuallyDrop, ops::Deref, rc::Rc},
crate::gfx_apis::vulkan::descriptor::VulkanDescriptorSetLayout, ash::vk::DeviceSize,
std::ops::Deref,
};
pub struct VulkanDescriptorBufferCache {
device: Rc<VulkanDevice>,
allocator: Rc<VulkanAllocator>,
buffers: RefCell<Vec<VulkanDescriptorBufferUnused>>,
has_sampler: bool,
}
pub struct VulkanDescriptorBuffer {
cache: Rc<VulkanDescriptorBufferCache>,
pub buffer: ManuallyDrop<VulkanDescriptorBufferUnused>,
}
pub struct VulkanDescriptorBufferUnused {
device: Rc<VulkanDevice>,
pub size: DeviceSize,
pub buffer: Buffer,
pub allocation: VulkanAllocation,
pub address: DeviceAddress,
}
#[derive(Default)]
pub struct VulkanDescriptorBufferWriter {
buffer: Vec<u8>,
@ -45,124 +12,6 @@ pub struct VulkanDescriptorBufferSetWriter<'a> {
set: &'a mut [u8],
}
impl VulkanDescriptorBufferCache {
pub fn new(
device: &Rc<VulkanDevice>,
allocator: &Rc<VulkanAllocator>,
has_sampler: bool,
) -> Self {
Self {
device: device.clone(),
allocator: allocator.clone(),
buffers: Default::default(),
has_sampler,
}
}
pub fn allocate(
self: &Rc<Self>,
capacity: DeviceSize,
) -> Result<VulkanDescriptorBuffer, VulkanError> {
const MIN_ALLOCATION: DeviceSize = 1024;
let capacity = capacity.max(MIN_ALLOCATION);
let mut smallest = None;
let mut smallest_size = DeviceSize::MAX;
let mut fitting = None;
let mut fitting_size = DeviceSize::MAX;
let buffers = &mut *self.buffers.borrow_mut();
for (idx, buffer) in buffers.iter().enumerate() {
if buffer.size >= capacity {
if buffer.size < fitting_size {
fitting = Some(idx);
fitting_size = buffer.size;
}
} else {
if buffer.size < smallest_size {
smallest = Some(idx);
smallest_size = buffer.size;
}
}
}
if let Some(idx) = fitting {
return Ok(VulkanDescriptorBuffer {
cache: self.clone(),
buffer: ManuallyDrop::new(buffers.swap_remove(idx)),
});
}
if let Some(idx) = smallest {
log::debug!("discarding size {}", smallest_size);
buffers.swap_remove(idx);
}
let size = capacity.checked_next_power_of_two().unwrap();
log::debug!("allocating size {}", size);
let buffer = {
let usage = self.usage();
let info = BufferCreateInfo::default().size(size).usage(usage);
unsafe {
self.device
.device
.create_buffer(&info, None)
.map_err(VulkanError::CreateBuffer)?
}
};
let destroy_buffer = OnDrop(|| unsafe { self.device.device.destroy_buffer(buffer, None) });
let memory_requirements =
unsafe { self.device.device.get_buffer_memory_requirements(buffer) };
let allocation = {
let flags = UsageFlags::UPLOAD
| UsageFlags::FAST_DEVICE_ACCESS
| UsageFlags::HOST_ACCESS
| UsageFlags::DEVICE_ADDRESS;
self.allocator.alloc(&memory_requirements, flags, true)?
};
unsafe {
self.device
.device
.bind_buffer_memory(buffer, allocation.memory, allocation.offset)
.map_err(VulkanError::BindBufferMemory)?;
}
destroy_buffer.forget();
let address = {
let info = BufferDeviceAddressInfo::default().buffer(buffer);
unsafe { self.device.device.get_buffer_device_address(&info) }
};
Ok(VulkanDescriptorBuffer {
cache: self.clone(),
buffer: ManuallyDrop::new(VulkanDescriptorBufferUnused {
device: self.device.clone(),
size,
buffer,
allocation,
address,
}),
})
}
pub fn usage(&self) -> BufferUsageFlags {
let mut usage = BufferUsageFlags::RESOURCE_DESCRIPTOR_BUFFER_EXT
| BufferUsageFlags::SHADER_DEVICE_ADDRESS;
if self.has_sampler {
usage |= BufferUsageFlags::SAMPLER_DESCRIPTOR_BUFFER_EXT;
}
usage
}
}
impl Drop for VulkanDescriptorBuffer {
fn drop(&mut self) {
let buffer = unsafe { ManuallyDrop::take(&mut self.buffer) };
self.cache.buffers.borrow_mut().push(buffer);
}
}
impl Drop for VulkanDescriptorBufferUnused {
fn drop(&mut self) {
unsafe {
self.device.device.destroy_buffer(self.buffer, None);
}
}
}
impl VulkanDescriptorBufferWriter {
pub fn clear(&mut self) {
self.buffer.clear();

View file

@ -39,7 +39,8 @@ pub(super) struct PipelineCreateInfo {
pub(super) blend: bool,
pub(super) src_has_alpha: bool,
pub(super) has_alpha_mult: bool,
pub(super) with_linear_output: bool,
pub(super) eotf: u32,
pub(super) oetf: u32,
pub(super) frag_descriptor_set_layout: Option<Rc<VulkanDescriptorSetLayout>>,
}
@ -48,13 +49,13 @@ impl VulkanDevice {
&self,
info: PipelineCreateInfo,
) -> Result<Rc<VulkanPipeline>, VulkanError> {
self.create_pipeline_(info, size_of::<P>() as _)
self.create_pipeline2(info, size_of::<P>())
}
fn create_pipeline_(
pub(super) fn create_pipeline2(
&self,
info: PipelineCreateInfo,
push_size: u32,
push_size: usize,
) -> Result<Rc<VulkanPipeline>, VulkanError> {
let pipeline_layout = {
let mut push_constant_ranges = ArrayVec::<_, 1>::new();
@ -63,7 +64,7 @@ impl VulkanDevice {
PushConstantRange::default()
.stage_flags(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT)
.offset(0)
.size(push_size),
.size(push_size as u32),
);
}
let mut descriptor_set_layouts = ArrayVec::<_, 1>::new();
@ -77,8 +78,8 @@ impl VulkanDevice {
};
let destroy_layout =
OnDrop(|| unsafe { self.device.destroy_pipeline_layout(pipeline_layout, None) });
let mut frag_spec_data = ArrayVec::<_, { 3 * 4 }>::new();
let mut frag_spec_entries = ArrayVec::<_, 3>::new();
let mut frag_spec_data = ArrayVec::<_, { 4 * 4 }>::new();
let mut frag_spec_entries = ArrayVec::<_, 4>::new();
let mut frag_spec_entry = |data: &[u8]| {
let entry = SpecializationMapEntry::default()
.constant_id(frag_spec_entries.len() as _)
@ -89,7 +90,8 @@ impl VulkanDevice {
};
frag_spec_entry(&(info.src_has_alpha as u32).to_ne_bytes());
frag_spec_entry(&(info.has_alpha_mult as u32).to_ne_bytes());
frag_spec_entry(&(info.with_linear_output as u32).to_ne_bytes());
frag_spec_entry(&info.eotf.to_ne_bytes());
frag_spec_entry(&info.oetf.to_ne_bytes());
let frag_spec = SpecializationInfo::default()
.map_entries(&frag_spec_entries)
.data(&frag_spec_data);

View file

@ -10,11 +10,10 @@ use {
gfx_apis::vulkan::{
VulkanError,
allocator::{VulkanAllocator, VulkanThreadedAllocator},
buffer_cache::{VulkanBuffer, VulkanBufferCache},
command::{VulkanCommandBuffer, VulkanCommandPool},
descriptor::VulkanDescriptorSetLayout,
descriptor_buffer::{
VulkanDescriptorBuffer, VulkanDescriptorBufferCache, VulkanDescriptorBufferWriter,
},
descriptor_buffer::VulkanDescriptorBufferWriter,
device::VulkanDevice,
fence::VulkanFence,
image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory},
@ -22,17 +21,17 @@ use {
sampler::VulkanSampler,
semaphore::VulkanSemaphore,
shaders::{
FILL_FRAG, FILL_VERT, FillPushConstants, OUT_FRAG, OUT_VERT, OutPushConstants,
TEX_FRAG, TEX_VERT, TexPushConstants, VulkanShader,
FILL_FRAG, FILL_VERT, FillPushConstants, LEGACY_FILL_FRAG, LEGACY_FILL_VERT,
LEGACY_TEX_FRAG, LEGACY_TEX_VERT, LegacyFillPushConstants, LegacyTexPushConstants,
OUT_FRAG, OUT_VERT, OutPushConstants, TEX_FRAG, TEX_VERT, TexPushConstants,
TexVertex, VulkanShader,
},
transfer_functions::{TF_LINEAR, TF_SRGB},
},
io_uring::IoUring,
rect::{Rect, Region},
theme::Color,
utils::{
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once,
stack::Stack,
},
theme::{Color, TransferFunction},
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack},
video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file},
},
ahash::AHashMap,
@ -40,26 +39,29 @@ use {
ash::{
Device,
vk::{
self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, ClearAttachment,
ClearColorValue, ClearRect, ClearValue, CommandBuffer, CommandBufferBeginInfo,
CommandBufferSubmitInfo, CommandBufferUsageFlags, CopyImageInfo2, DependencyInfoKHR,
DescriptorBufferBindingInfoEXT, DescriptorImageInfo, DescriptorType, DeviceSize,
Extent2D, Extent3D, ImageAspectFlags, ImageCopy2, ImageLayout, ImageMemoryBarrier2,
ImageSubresourceLayers, ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint,
PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, Rect2D, RenderingAttachmentInfo,
RenderingInfo, SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags,
SubmitInfo2, Viewport, WriteDescriptorSet,
self, AccessFlags2, AttachmentLoadOp, AttachmentStoreOp, BufferUsageFlags,
ClearAttachment, ClearColorValue, ClearRect, ClearValue, CommandBuffer,
CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags,
CopyImageInfo2, DependencyInfoKHR, DescriptorBufferBindingInfoEXT, DescriptorImageInfo,
DescriptorType, DeviceAddress, DeviceSize, Extent2D, Extent3D, ImageAspectFlags,
ImageCopy2, ImageLayout, ImageMemoryBarrier2, ImageSubresourceLayers,
ImageSubresourceRange, Offset2D, Offset3D, PipelineBindPoint, PipelineStageFlags2,
QUEUE_FAMILY_FOREIGN_EXT, Rect2D, RenderingAttachmentInfo, RenderingInfo,
SemaphoreSubmitInfo, SemaphoreSubmitInfoKHR, ShaderStageFlags, SubmitInfo2, Viewport,
WriteDescriptorSet,
},
},
isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt},
jay_algorithms::rect::Tag,
linearize::{Linearize, StaticMap, static_map},
linearize::{Linearize, LinearizeExt, StaticMap, static_map},
std::{
borrow::Cow,
cell::{Cell, RefCell},
collections::hash_map::Entry,
fmt::{Debug, Formatter},
mem, ptr,
mem,
ops::Range,
ptr,
rc::{Rc, Weak},
slice,
},
@ -94,9 +96,10 @@ pub struct VulkanRenderer {
pub(super) pending_cpu_jobs: CopyHashMap<u64, PendingJob>,
pub(super) shm_allocator: Rc<VulkanThreadedAllocator>,
pub(super) sampler: Rc<VulkanSampler>,
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) resource_descriptor_buffer_cache: Rc<VulkanDescriptorBufferCache>,
pub(super) sampler_descriptor_buffer_cache: Rc<VulkanBufferCache>,
pub(super) resource_descriptor_buffer_cache: Rc<VulkanBufferCache>,
pub(super) blend_buffers: RefCell<AHashMap<(u32, u32), Weak<VulkanImage>>>,
pub(super) shader_buffer_cache: Rc<VulkanBufferCache>,
}
pub(super) struct CachedCommandBuffers {
@ -126,13 +129,13 @@ pub(super) struct UsedTexture {
release_sync: ReleaseSync,
}
#[derive(Linearize)]
#[derive(Copy, Clone, Linearize)]
pub(super) enum TexCopyType {
Identity,
Multiply,
}
#[derive(Linearize)]
#[derive(Copy, Clone, Linearize)]
pub(super) enum TexSourceType {
Opaque,
HasAlpha,
@ -148,7 +151,8 @@ pub(super) struct Memory {
wait_semaphore_infos: Vec<SemaphoreSubmitInfo<'static>>,
release_fence: Option<Rc<VulkanFence>>,
release_sync_file: Option<SyncFile>,
descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
used_buffers: ArrayVec<VulkanBuffer, 3>,
paint_bounds: StaticMap<RenderPass, Option<PaintRegion>>,
paint_regions: StaticMap<RenderPass, Vec<PaintRegion>>,
clear_rects: StaticMap<RenderPass, Vec<ClearRect>>,
image_copy_regions: Vec<ImageCopy2<'static>>,
@ -156,6 +160,40 @@ pub(super) struct Memory {
resource_descriptor_buffer_writer: VulkanDescriptorBufferWriter,
regions_1: Vec<Rect>,
regions_2: Vec<Rect<u32>>,
ops: StaticMap<RenderPass, Vec<VulkanOp>>,
ops_tmp: StaticMap<RenderPass, Vec<VulkanOp>>,
fill_targets: Vec<Point>,
tex_targets: Vec<[Point; 2]>,
data_buffer: Vec<u8>,
out_address: DeviceAddress,
}
type Point = [[f32; 2]; 4];
enum VulkanOp {
Fill(VulkanFillOp),
Tex(VulkanTexOp),
}
struct VulkanTexOp {
tex: Rc<VulkanImage>,
range: Range<usize>,
buffer_resv: Option<Rc<dyn BufferResv>>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
alpha: f32,
source_type: TexSourceType,
copy_type: TexCopyType,
range_address: DeviceAddress,
instances: u32,
}
struct VulkanFillOp {
range: Range<usize>,
color: [f32; 4],
source_type: TexSourceType,
range_address: DeviceAddress,
instances: u32,
}
#[derive(Copy, Clone, Debug, Linearize, Eq, PartialEq)]
@ -164,6 +202,7 @@ pub(super) enum RenderPass {
FrameBuffer,
}
#[derive(Copy, Clone)]
struct PaintRegion {
x1: f32,
y1: f32,
@ -181,7 +220,7 @@ pub(super) struct PendingFrame {
wait_semaphores: Cell<Vec<Rc<VulkanSemaphore>>>,
waiter: Cell<Option<SpawnedFuture<()>>>,
_release_fence: Option<Rc<VulkanFence>>,
_descriptor_buffers: ArrayVec<VulkanDescriptorBuffer, 2>,
_used_buffers: ArrayVec<VulkanBuffer, 3>,
}
pub(super) struct VulkanFormatPipelines {
@ -195,8 +234,21 @@ impl VulkanDevice {
eng: &Rc<AsyncEngine>,
ring: &Rc<IoUring>,
) -> Result<Rc<VulkanRenderer>, VulkanError> {
let fill_vert_shader = self.create_shader(FILL_VERT)?;
let fill_frag_shader = self.create_shader(FILL_FRAG)?;
let fill_vert_shader;
let fill_frag_shader;
let tex_vert_shader;
let tex_frag_shader;
if self.descriptor_buffer.is_some() {
tex_vert_shader = self.create_shader(TEX_VERT)?;
tex_frag_shader = self.create_shader(TEX_FRAG)?;
fill_vert_shader = self.create_shader(FILL_VERT)?;
fill_frag_shader = self.create_shader(FILL_FRAG)?;
} else {
tex_vert_shader = self.create_shader(LEGACY_TEX_VERT)?;
tex_frag_shader = self.create_shader(LEGACY_TEX_FRAG)?;
fill_vert_shader = self.create_shader(LEGACY_FILL_VERT)?;
fill_frag_shader = self.create_shader(LEGACY_FILL_FRAG)?;
}
let sampler = self.create_sampler()?;
let tex_descriptor_set_layout = self.create_tex_descriptor_set_layout(&sampler)?;
let out_descriptor_set_layout = self
@ -206,8 +258,6 @@ impl VulkanDevice {
.transpose()?;
let out_vert_shader = self.create_shader(OUT_VERT)?;
let out_frag_shader = self.create_shader(OUT_FRAG)?;
let tex_vert_shader = self.create_shader(TEX_VERT)?;
let tex_frag_shader = self.create_shader(TEX_FRAG)?;
let gfx_command_buffers = self.create_command_pool(self.graphics_queue_idx)?;
let transfer_command_buffers = self
.distinct_transfer_queue_family_idx
@ -247,9 +297,14 @@ impl VulkanDevice {
let allocator = self.create_allocator()?;
let shm_allocator = self.create_threaded_allocator()?;
let sampler_descriptor_buffer_cache =
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, true));
VulkanBufferCache::for_descriptor_buffer(self, &allocator, true);
let resource_descriptor_buffer_cache =
Rc::new(VulkanDescriptorBufferCache::new(self, &allocator, false));
VulkanBufferCache::for_descriptor_buffer(self, &allocator, false);
let shader_buffer_cache = {
// TODO: https://github.com/KhronosGroup/Vulkan-Samples/issues/1286
let usage = BufferUsageFlags::SHADER_DEVICE_ADDRESS | BufferUsageFlags::STORAGE_BUFFER;
VulkanBufferCache::new(self, &allocator, usage)
};
let render = Rc::new(VulkanRenderer {
formats: Rc::new(formats),
device: self.clone(),
@ -281,6 +336,7 @@ impl VulkanDevice {
sampler_descriptor_buffer_cache,
resource_descriptor_buffer_cache,
blend_buffers: Default::default(),
shader_buffer_cache,
});
render.get_or_create_pipelines(XRGB8888.vk_format, RenderPass::FrameBuffer)?;
Ok(render)
@ -293,38 +349,53 @@ impl VulkanRenderer {
format: vk::Format,
pass: RenderPass,
) -> Result<Rc<VulkanFormatPipelines>, VulkanError> {
let with_linear_output = pass == RenderPass::BlendBuffer;
let (eotf, oetf) = match pass {
RenderPass::BlendBuffer => (TF_SRGB, TF_LINEAR),
RenderPass::FrameBuffer => (TF_SRGB, TF_SRGB),
};
let pipelines = &self.pipelines[pass];
if let Some(pl) = pipelines.get(&format) {
return Ok(pl);
}
let create_fill_pipeline = |src_has_alpha| {
self.device
.create_pipeline::<FillPushConstants>(PipelineCreateInfo {
format,
vert: self.fill_vert_shader.clone(),
frag: self.fill_frag_shader.clone(),
blend: src_has_alpha,
src_has_alpha,
has_alpha_mult: false,
with_linear_output,
frag_descriptor_set_layout: None,
})
let push_size = if self.device.descriptor_buffer.is_some() {
size_of::<FillPushConstants>()
} else {
size_of::<LegacyFillPushConstants>()
};
let info = PipelineCreateInfo {
format,
vert: self.fill_vert_shader.clone(),
frag: self.fill_frag_shader.clone(),
blend: src_has_alpha,
src_has_alpha,
has_alpha_mult: false,
eotf,
oetf,
frag_descriptor_set_layout: None,
};
self.device.create_pipeline2(info, push_size)
};
let fill_opaque = create_fill_pipeline(false)?;
let fill_alpha = create_fill_pipeline(true)?;
let create_tex_pipeline = |src_has_alpha, has_alpha_mult| {
self.device
.create_pipeline::<TexPushConstants>(PipelineCreateInfo {
format,
vert: self.tex_vert_shader.clone(),
frag: self.tex_frag_shader.clone(),
blend: src_has_alpha || has_alpha_mult,
src_has_alpha,
has_alpha_mult,
with_linear_output,
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()),
})
let push_size = if self.device.descriptor_buffer.is_some() {
size_of::<TexPushConstants>()
} else {
size_of::<LegacyTexPushConstants>()
};
let info = PipelineCreateInfo {
format,
vert: self.tex_vert_shader.clone(),
frag: self.tex_frag_shader.clone(),
blend: src_has_alpha || has_alpha_mult,
src_has_alpha,
has_alpha_mult,
eotf,
oetf,
frag_descriptor_set_layout: Some(self.tex_descriptor_set_layout.clone()),
};
self.device.create_pipeline2(info, push_size)
};
let tex_opaque = create_tex_pipeline(false, false)?;
let tex_alpha = create_tex_pipeline(true, false)?;
@ -357,7 +428,6 @@ impl VulkanRenderer {
fn create_descriptor_buffers(
&self,
buf: CommandBuffer,
opts: &[GfxApiOpt],
bb: Option<&VulkanImage>,
) -> Result<(), VulkanError> {
let Some(db) = &self.device.descriptor_buffer else {
@ -366,7 +436,6 @@ impl VulkanRenderer {
zone!("create_descriptor_buffers");
let version = self.allocate_point();
let memory = &mut *self.memory.borrow_mut();
memory.descriptor_buffers.clear();
let sampler_writer = &mut memory.sampler_descriptor_buffer_writer;
sampler_writer.clear();
let resource_writer = &mut memory.resource_descriptor_buffer_writer;
@ -378,28 +447,30 @@ impl VulkanRenderer {
let mut writer = resource_writer.add_set(layout);
writer.write(layout.offsets[0], &bb.sampled_image_descriptor);
}
for cmd in opts {
let GfxApiOpt::CopyTexture(c) = cmd else {
continue;
};
let tex = c.tex.clone().into_vk(&self.device.device);
if tex.descriptor_buffer_version.replace(version) == version {
continue;
for pass in RenderPass::variants() {
for cmd in &memory.ops[pass] {
let VulkanOp::Tex(c) = cmd else {
continue;
};
let tex = &c.tex;
if tex.descriptor_buffer_version.replace(version) == version {
continue;
}
let offset = sampler_writer.next_offset();
tex.descriptor_buffer_offset.set(offset);
let mut writer = sampler_writer.add_set(&self.tex_descriptor_set_layout);
writer.write(
self.tex_descriptor_set_layout.offsets[0],
&tex.shader_read_only_optimal_descriptor,
);
}
let offset = sampler_writer.next_offset();
tex.descriptor_buffer_offset.set(offset);
let mut writer = sampler_writer.add_set(&self.tex_descriptor_set_layout);
writer.write(
self.tex_descriptor_set_layout.offsets[0],
&tex.shader_read_only_optimal_descriptor,
);
}
let mut infos = ArrayVec::<_, 2>::new();
for (writer, cache) in [
(&sampler_writer, &self.sampler_descriptor_buffer_cache),
(&resource_writer, &self.resource_descriptor_buffer_cache),
] {
let buffer = cache.allocate(writer.len() as DeviceSize)?;
let buffer = cache.allocate(writer.len() as DeviceSize, 1)?;
buffer.buffer.allocation.upload(|ptr, _| unsafe {
ptr::copy_nonoverlapping(writer.as_ptr(), ptr, writer.len())
})?;
@ -407,7 +478,7 @@ impl VulkanRenderer {
.usage(cache.usage())
.address(buffer.buffer.address);
infos.push(info);
memory.descriptor_buffers.push(buffer);
memory.used_buffers.push(buffer);
}
unsafe {
db.cmd_bind_descriptor_buffers(buf, &infos);
@ -415,35 +486,246 @@ impl VulkanRenderer {
Ok(())
}
fn collect_memory(&self, opts: &[GfxApiOpt]) {
fn convert_ops(&self, opts: &[GfxApiOpt]) {
zone!("convert_ops");
let memory = &mut *self.memory.borrow_mut();
for ops in memory.ops.values_mut() {
ops.clear();
}
for ops in memory.ops_tmp.values_mut() {
ops.clear();
}
memory.tex_targets.clear();
memory.fill_targets.clear();
memory.data_buffer.clear();
let sync = |memory: &mut Memory| {
for pass in RenderPass::variants() {
let ops = &mut memory.ops_tmp[pass];
ops.sort_unstable_by_key(|o| {
#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum Key {
Fill { color: [u32; 4] },
Tex,
}
match o {
VulkanOp::Fill(f) => Key::Fill {
color: f.color.map(|c| c.to_bits()),
},
VulkanOp::Tex(_) => Key::Tex,
}
});
let mops = &mut memory.ops[pass];
if self.device.descriptor_buffer.is_none() {
mops.append(ops);
continue;
}
for (idx, op) in ops.drain(..).enumerate() {
match op {
VulkanOp::Fill(mut f) => {
f.range_address = memory.data_buffer.len() as DeviceAddress;
f.instances = f.range.len() as u32;
for pos in &memory.fill_targets[f.range.clone()] {
memory.data_buffer.extend_from_slice(uapi::as_bytes(pos));
}
if let Some(VulkanOp::Fill(p)) = mops.last_mut() {
if p.color == f.color && idx > 0 {
p.instances += f.instances;
continue;
}
}
mops.push(VulkanOp::Fill(f));
}
VulkanOp::Tex(mut c) => {
c.range_address = memory.data_buffer.len() as DeviceAddress;
c.instances = c.range.len() as u32;
for &[pos, tex_pos] in &memory.tex_targets[c.range.clone()] {
let vertex = TexVertex { pos, tex_pos };
memory
.data_buffer
.extend_from_slice(uapi::as_bytes(&vertex));
}
mops.push(VulkanOp::Tex(c));
}
}
}
}
};
for op in opts {
match op {
GfxApiOpt::Sync => {
sync(memory);
}
GfxApiOpt::FillRect(fr) => {
let target = fr.rect.to_points();
for pass in RenderPass::variants() {
let Some(bounds) = memory.paint_bounds[pass] else {
continue;
};
if !bounds.intersects(&target) {
continue;
}
let tf = match pass {
RenderPass::BlendBuffer => TransferFunction::Linear,
RenderPass::FrameBuffer => TransferFunction::Srgb,
};
let ops = &mut memory.ops_tmp[pass];
let lo = memory.fill_targets.len();
for region in &memory.paint_regions[pass] {
let mut target = target;
if !region.constrain(&mut target, None) {
continue;
}
memory.fill_targets.push(target);
}
let hi = memory.fill_targets.len();
if lo == hi {
continue;
}
let color = fr.color.to_array2(tf, fr.alpha);
let source_type = match color[3] < 1.0 {
false => TexSourceType::Opaque,
true => TexSourceType::HasAlpha,
};
ops.push(VulkanOp::Fill(VulkanFillOp {
range: lo..hi,
color,
source_type,
range_address: 0,
instances: 0,
}));
}
}
GfxApiOpt::CopyTexture(ct) => {
let tex = ct.tex.clone().into_vk(&self.device.device);
if tex.contents_are_undefined.get() {
log::warn!("Ignoring undefined texture");
continue;
}
if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible
{
log::warn!("Ignoring texture owned by different queue");
continue;
}
let target = ct.target.to_points();
let source = ct.source.to_points();
for pass in RenderPass::variants() {
let Some(bounds) = memory.paint_bounds[pass] else {
continue;
};
if !bounds.intersects(&target) {
continue;
}
let ops = &mut memory.ops_tmp[pass];
let lo = memory.tex_targets.len();
for region in &memory.paint_regions[pass] {
let mut target = target;
let mut source = source;
if !region.constrain(&mut target, Some(&mut source)) {
continue;
}
memory.tex_targets.push([target, source]);
}
let hi = memory.tex_targets.len();
if lo == hi {
continue;
}
let copy_type = match ct.alpha.is_some() {
true => TexCopyType::Multiply,
false => TexCopyType::Identity,
};
let source_type = match tex.format.has_alpha && !ct.opaque {
true => TexSourceType::HasAlpha,
false => TexSourceType::Opaque,
};
ops.push(VulkanOp::Tex(VulkanTexOp {
tex: tex.clone(),
range: lo..hi,
buffer_resv: ct.buffer_resv.clone(),
acquire_sync: ct.acquire_sync.clone(),
release_sync: ct.release_sync,
alpha: ct.alpha.unwrap_or_default(),
source_type,
copy_type,
range_address: 0,
instances: 0,
}));
}
}
}
}
sync(memory);
}
fn create_data_buffer(&self) -> Result<(), VulkanError> {
if self.device.descriptor_buffer.is_none() {
return Ok(());
}
zone!("create_data_buffer");
let memory = &mut *self.memory.borrow_mut();
let buf = &mut memory.data_buffer;
{
memory.out_address = buf.len() as _;
for region in &memory.paint_regions[RenderPass::BlendBuffer] {
buf.extend_from_slice(uapi::as_bytes(&[
[region.x2, region.y1],
[region.x1, region.y1],
[region.x2, region.y2],
[region.x1, region.y2],
]));
}
}
if buf.is_empty() {
return Ok(());
}
let buffer = self.shader_buffer_cache.allocate(buf.len() as _, 8)?;
buffer.buffer.allocation.upload(|ptr, _| unsafe {
ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len());
})?;
for ops in memory.ops.values_mut() {
for op in ops {
match op {
VulkanOp::Fill(f) => {
f.range_address += buffer.buffer.address;
}
VulkanOp::Tex(c) => {
c.range_address += buffer.buffer.address;
}
}
}
}
memory.out_address += buffer.buffer.address;
memory.used_buffers.push(buffer);
Ok(())
}
fn collect_memory(&self) {
zone!("collect_memory");
let mut memory = self.memory.borrow_mut();
let memory = &mut *self.memory.borrow_mut();
memory.dmabuf_sample.clear();
memory.queue_transfer.clear();
let execution = self.allocate_point();
for cmd in opts {
if let GfxApiOpt::CopyTexture(c) = cmd {
let tex = c.tex.clone().into_vk(&self.device.device);
if tex.contents_are_undefined.get() {
continue;
for pass in RenderPass::variants() {
for cmd in &memory.ops[pass] {
if let VulkanOp::Tex(c) = cmd {
let tex = &c.tex;
if tex.execution_version.replace(execution) == execution {
continue;
}
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
QueueTransfer::Unnecessary => {}
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
QueueTransfer::Impossible => continue,
}
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
memory.dmabuf_sample.push(tex.clone())
}
memory.textures.push(UsedTexture {
tex: tex.clone(),
resv: c.buffer_resv.clone(),
acquire_sync: c.acquire_sync.clone(),
release_sync: c.release_sync,
});
}
if tex.execution_version.replace(execution) == execution {
continue;
}
match tex.queue_state.get().acquire(QueueFamily::Gfx) {
QueueTransfer::Unnecessary => {}
QueueTransfer::Possible => memory.queue_transfer.push(tex.clone()),
QueueTransfer::Impossible => continue,
}
if let VulkanImageMemory::DmaBuf(_) = &tex.ty {
memory.dmabuf_sample.push(tex.clone())
}
memory.textures.push(UsedTexture {
tex,
resv: c.buffer_resv.clone(),
acquire_sync: c.acquire_sync.clone(),
release_sync: c.release_sync,
});
}
}
}
@ -571,8 +853,8 @@ impl VulkanRenderer {
let clear_value = ClearValue {
color: ClearColorValue {
float32: match pass {
RenderPass::BlendBuffer => clear.to_array_linear(None),
RenderPass::FrameBuffer => clear.to_array_srgb(None),
RenderPass::BlendBuffer => clear.to_array(TransferFunction::Linear),
RenderPass::FrameBuffer => clear.to_array(TransferFunction::Srgb),
},
},
};
@ -662,12 +944,10 @@ impl VulkanRenderer {
&self,
buf: CommandBuffer,
target: &VulkanImage,
opts: &[GfxApiOpt],
pass: RenderPass,
) -> Result<(), VulkanError> {
zone!("record_draws");
let memory = &*self.memory.borrow();
let paint_regions = &memory.paint_regions[pass];
let pipelines = self.get_or_create_pipelines(target.format.vk_format, pass)?;
let dev = &self.device.device;
let mut current_pipeline = None;
@ -679,29 +959,18 @@ impl VulkanRenderer {
}
}
};
for opt in opts {
for opt in &memory.ops[pass] {
match opt {
GfxApiOpt::Sync => {}
GfxApiOpt::FillRect(r) => {
let push = FillPushConstants {
pos: r.rect.to_points(),
color: match pass {
RenderPass::BlendBuffer => r.color.to_array_linear(r.alpha),
RenderPass::FrameBuffer => r.color.to_array_srgb(r.alpha),
},
};
let source_type = match push.color[3] < 1.0 {
true => TexSourceType::HasAlpha,
false => TexSourceType::Opaque,
};
let pipeline = &pipelines.fill[source_type];
for region in paint_regions {
let mut push = push;
let draw = region.constrain(&mut push.pos, None);
if !draw {
continue;
}
bind(pipeline);
VulkanOp::Fill(r) => {
let pipeline = &pipelines.fill[r.source_type];
bind(pipeline);
if self.device.descriptor_buffer.is_some() {
let push = FillPushConstants {
color: r.color,
vertices: r.range_address,
_padding1: 0,
_padding2: 0,
};
unsafe {
dev.cmd_push_constants(
buf,
@ -710,70 +979,48 @@ impl VulkanRenderer {
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, 1, 0, 0);
dev.cmd_draw(buf, 4, r.instances, 0, 0);
}
} else {
for &pos in &memory.fill_targets[r.range.clone()] {
let push = LegacyFillPushConstants {
pos,
color: r.color,
};
unsafe {
dev.cmd_push_constants(
buf,
pipeline.pipeline_layout,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, 1, 0, 0);
}
}
}
}
GfxApiOpt::CopyTexture(c) => {
let tex = c.tex.as_vk(&self.device.device);
if tex.contents_are_undefined.get() {
log::warn!("Ignoring undefined texture");
continue;
}
if tex.queue_state.get().acquire(QueueFamily::Gfx) == QueueTransfer::Impossible
{
log::warn!("Ignoring texture owned by different queue");
continue;
}
let copy_type = match c.alpha.is_some() {
true => TexCopyType::Multiply,
false => TexCopyType::Identity,
};
let source_type = match tex.format.has_alpha && !c.opaque {
true => TexSourceType::HasAlpha,
false => TexSourceType::Opaque,
};
let pipeline = &pipelines.tex[copy_type][source_type];
let push = TexPushConstants {
pos: c.target.to_points(),
tex_pos: c.source.to_points(),
alpha: c.alpha.unwrap_or_default(),
};
VulkanOp::Tex(c) => {
let tex = &c.tex;
let pipeline = &pipelines.tex[c.copy_type][c.source_type];
bind(pipeline);
let image_info = DescriptorImageInfo::default()
.image_view(tex.texture_view)
.image_layout(ImageLayout::SHADER_READ_ONLY_OPTIMAL);
let init = Once::default();
for region in paint_regions {
let mut push = push;
let draw = region.constrain(&mut push.pos, Some(&mut push.tex_pos));
if !draw {
continue;
}
init.exec(|| unsafe {
bind(pipeline);
if let Some(db) = &self.device.descriptor_buffer {
db.cmd_set_descriptor_buffer_offsets(
buf,
PipelineBindPoint::GRAPHICS,
pipeline.pipeline_layout,
0,
&[0],
&[tex.descriptor_buffer_offset.get()],
);
} else {
let write_descriptor_set = WriteDescriptorSet::default()
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(slice::from_ref(&image_info));
self.device.push_descriptor.cmd_push_descriptor_set(
buf,
PipelineBindPoint::GRAPHICS,
pipeline.pipeline_layout,
0,
slice::from_ref(&write_descriptor_set),
);
}
});
if let Some(db) = &self.device.descriptor_buffer {
let push = TexPushConstants {
vertices: c.range_address,
alpha: c.alpha,
};
unsafe {
db.cmd_set_descriptor_buffer_offsets(
buf,
PipelineBindPoint::GRAPHICS,
pipeline.pipeline_layout,
0,
&[0],
&[tex.descriptor_buffer_offset.get()],
);
dev.cmd_push_constants(
buf,
pipeline.pipeline_layout,
@ -781,7 +1028,37 @@ impl VulkanRenderer {
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, 1, 0, 0);
dev.cmd_draw(buf, 4, c.instances, 0, 0);
}
} else {
let write_descriptor_set = WriteDescriptorSet::default()
.descriptor_type(DescriptorType::COMBINED_IMAGE_SAMPLER)
.image_info(slice::from_ref(&image_info));
unsafe {
self.device.push_descriptor.cmd_push_descriptor_set(
buf,
PipelineBindPoint::GRAPHICS,
pipeline.pipeline_layout,
0,
slice::from_ref(&write_descriptor_set),
);
}
for &[pos, tex_pos] in &memory.tex_targets[c.range.clone()] {
let push = LegacyTexPushConstants {
pos,
tex_pos,
alpha: c.alpha,
};
unsafe {
dev.cmd_push_constants(
buf,
pipeline.pipeline_layout,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, 1, 0, 0);
}
}
}
}
@ -835,13 +1112,18 @@ impl VulkanRenderer {
blend: false,
src_has_alpha: true,
has_alpha_mult: false,
with_linear_output: true,
eotf: TF_LINEAR,
oetf: TF_SRGB,
frag_descriptor_set_layout: Some(layout.clone()),
})?;
e.insert(out.clone());
out
}
};
let push = OutPushConstants {
vertices: memory.out_address,
};
let instances = memory.paint_regions[RenderPass::BlendBuffer].len() as u32;
let dev = &self.device.device;
unsafe {
dev.cmd_bind_pipeline(buf, PipelineBindPoint::GRAPHICS, pipeline.pipeline);
@ -853,26 +1135,14 @@ impl VulkanRenderer {
&[1],
&[bb.descriptor_buffer_offset.get()],
);
}
for region in &memory.paint_regions[RenderPass::BlendBuffer] {
let push = OutPushConstants {
pos: [
[region.x2, region.y1],
[region.x1, region.y1],
[region.x2, region.y2],
[region.x1, region.y2],
],
};
unsafe {
dev.cmd_push_constants(
buf,
pipeline.pipeline_layout,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, 1, 0, 0);
}
dev.cmd_push_constants(
buf,
pipeline.pipeline_layout,
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
0,
uapi::as_bytes(&push),
);
dev.cmd_draw(buf, 4, instances, 0, 0);
}
Ok(())
}
@ -1215,7 +1485,7 @@ impl VulkanRenderer {
wait_semaphores: Cell::new(mem::take(&mut memory.wait_semaphores)),
waiter: Cell::new(None),
_release_fence: memory.release_fence.take(),
_descriptor_buffers: mem::take(&mut memory.descriptor_buffers),
_used_buffers: mem::take(&mut memory.used_buffers),
});
self.pending_frames.set(frame.point, frame.clone());
let future = self.eng.spawn(
@ -1257,7 +1527,7 @@ impl VulkanRenderer {
memory.queue_transfer.clear();
memory.wait_semaphores.clear();
memory.release_fence.take();
memory.descriptor_buffers.clear();
memory.used_buffers.clear();
memory.release_sync_file.take()
};
res.map(|_| sync_file)
@ -1290,7 +1560,7 @@ impl VulkanRenderer {
for opt in opts.iter().rev() {
let (opaque, fb_rect) = match opt {
GfxApiOpt::Sync => continue,
GfxApiOpt::FillRect(f) => (f.effective_color().a >= 1.0, f.rect),
GfxApiOpt::FillRect(f) => (f.effective_color().is_opaque(), f.rect),
GfxApiOpt::CopyTexture(c) => {
let opaque = 'opaque: {
if let Some(a) = c.alpha {
@ -1349,6 +1619,18 @@ impl VulkanRenderer {
y2: to_fb(y2, fb.height),
});
}
for pass in RenderPass::variants() {
let regions = &memory.paint_regions[pass];
if regions.is_empty() {
memory.paint_bounds[pass] = None;
} else {
let mut union = regions[0];
for region in &regions[1..] {
union = union.union(region);
}
memory.paint_bounds[pass] = Some(union);
}
}
let blend_clear = clear_region.intersect(&Region::from_rects2(&memory.regions_1));
let opaque_clear = clear_region.subtract_cow(&blend_clear);
// if bb.is_none() {
@ -1407,23 +1689,25 @@ impl VulkanRenderer {
self.elide_blend_buffer(&mut blend_buffer);
let bb = blend_buffer.as_deref();
let buf = self.gfx_command_buffers.allocate()?;
self.collect_memory(opts);
self.convert_ops(opts);
self.create_data_buffer()?;
self.collect_memory();
self.begin_command_buffer(buf.buffer)?;
self.create_descriptor_buffers(buf.buffer, opts, bb)?;
self.create_descriptor_buffers(buf.buffer, bb)?;
self.initial_barriers(buf.buffer, fb)?;
self.set_viewport(buf.buffer, fb);
if let Some(bb) = bb {
zone!("blend buffer pass");
self.blend_buffer_initial_barrier(buf.buffer, bb);
self.begin_rendering(buf.buffer, bb, clear, RenderPass::BlendBuffer);
self.record_draws(buf.buffer, bb, opts, RenderPass::BlendBuffer)?;
self.record_draws(buf.buffer, bb, RenderPass::BlendBuffer)?;
self.end_rendering(buf.buffer);
self.blend_buffer_final_barrier(buf.buffer, bb);
}
{
zone!("frame buffer pass");
self.begin_rendering(buf.buffer, fb, clear, RenderPass::FrameBuffer);
self.record_draws(buf.buffer, fb, opts, RenderPass::FrameBuffer)?;
self.record_draws(buf.buffer, fb, RenderPass::FrameBuffer)?;
if let Some(bb) = bb {
self.blend_buffer_copy(buf.buffer, fb, bb)?;
}
@ -1558,7 +1842,31 @@ async fn await_release(
}
impl PaintRegion {
fn constrain(&self, pos: &mut [[f32; 2]; 4], tex_pos: Option<&mut [[f32; 2]; 4]>) -> bool {
fn intersects(&self, pos: &Point) -> bool {
let mut p = *pos;
for [x, y] in &mut p {
*x = x.clamp(self.x1, self.x2);
*y = y.clamp(self.y1, self.y2);
}
if p[0] == p[1] && p[2] == p[3] {
return false;
}
if p[0] == p[2] && p[1] == p[3] {
return false;
}
true
}
fn union(&self, other: &Self) -> Self {
Self {
x1: self.x1.min(other.x1),
y1: self.y1.min(other.y1),
x2: self.x2.max(other.x2),
y2: self.y2.max(other.y2),
}
}
fn constrain(&self, pos: &mut Point, tex_pos: Option<&mut Point>) -> bool {
zone!("constrain");
let mut npos = *pos;
for [x, y] in &mut npos {

View file

@ -1,6 +1,6 @@
use {
crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice},
ash::vk::{ShaderModule, ShaderModuleCreateInfo},
ash::vk::{DeviceAddress, ShaderModule, ShaderModuleCreateInfo},
std::rc::Rc,
uapi::Packed,
};
@ -11,6 +11,12 @@ pub const TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.vert.s
pub const TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/tex.frag.spv"));
pub const OUT_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.vert.spv"));
pub const OUT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/out.frag.spv"));
pub const LEGACY_FILL_VERT: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/legacy_fill.vert.spv"));
pub const LEGACY_FILL_FRAG: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/legacy_fill.frag.spv"));
pub const LEGACY_TEX_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/legacy_tex.vert.spv"));
pub const LEGACY_TEX_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/legacy_tex.frag.spv"));
pub struct VulkanShader {
pub(super) device: Rc<VulkanDevice>,
@ -20,17 +26,36 @@ pub struct VulkanShader {
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct FillPushConstants {
pub pos: [[f32; 2]; 4],
pub color: [f32; 4],
pub vertices: DeviceAddress,
pub _padding1: u32,
pub _padding2: u32,
}
unsafe impl Packed for FillPushConstants {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct TexPushConstants {
pub struct LegacyFillPushConstants {
pub pos: [[f32; 2]; 4],
pub color: [f32; 4],
}
unsafe impl Packed for LegacyFillPushConstants {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct TexVertex {
pub pos: [[f32; 2]; 4],
pub tex_pos: [[f32; 2]; 4],
}
unsafe impl Packed for TexVertex {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct TexPushConstants {
pub vertices: DeviceAddress,
pub alpha: f32,
}
@ -38,8 +63,18 @@ unsafe impl Packed for TexPushConstants {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct OutPushConstants {
pub struct LegacyTexPushConstants {
pub pos: [[f32; 2]; 4],
pub tex_pos: [[f32; 2]; 4],
pub alpha: f32,
}
unsafe impl Packed for LegacyTexPushConstants {}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct OutPushConstants {
pub vertices: DeviceAddress,
}
unsafe impl Packed for OutPushConstants {}

View file

@ -1,4 +1,12 @@
#extension GL_EXT_buffer_reference : require
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
vec2 pos[][4];
};
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
layout(offset = 32) vec4 color;
vec4 color;
Vertices vertices;
uint padding1;
uint padding2;
} data;

View file

@ -1,6 +1,5 @@
#version 450
#include "frag_spec_const.glsl"
#include "fill.common.glsl"
layout(location = 0) out vec4 out_color;

View file

@ -1,16 +1,8 @@
#version 450
//#extension GL_EXT_debug_printf : enable
#include "fill.common.glsl"
void main() {
vec2 pos;
switch (gl_VertexIndex) {
case 0: pos = data.pos[0]; break;
case 1: pos = data.pos[1]; break;
case 2: pos = data.pos[2]; break;
case 3: pos = data.pos[3]; break;
}
vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
gl_Position = vec4(pos, 0.0, 1.0);
// debugPrintfEXT("gl_Position = %v4f", gl_Position);
}

View file

@ -3,6 +3,7 @@
layout(constant_id = 0) const bool src_has_alpha = false;
layout(constant_id = 1) const bool has_alpha_multiplier = false;
layout(constant_id = 2) const bool color_management = false;
layout(constant_id = 2) const uint eotf = 0;
layout(constant_id = 3) const uint oetf = 0;
#endif

View file

@ -0,0 +1,4 @@
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
layout(offset = 32) vec4 color;
} data;

View file

@ -0,0 +1,10 @@
#version 450
#include "../frag_spec_const.glsl"
#include "fill.common.glsl"
layout(location = 0) out vec4 out_color;
void main() {
out_color = data.color;
}

View file

@ -0,0 +1,16 @@
#version 450
//#extension GL_EXT_debug_printf : enable
#include "fill.common.glsl"
void main() {
vec2 pos;
switch (gl_VertexIndex) {
case 0: pos = data.pos[0]; break;
case 1: pos = data.pos[1]; break;
case 2: pos = data.pos[2]; break;
case 3: pos = data.pos[3]; break;
}
gl_Position = vec4(pos, 0.0, 1.0);
// debugPrintfEXT("gl_Position = %v4f", gl_Position);
}

View file

@ -0,0 +1,5 @@
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
layout(offset = 32) vec2 tex_pos[4];
layout(offset = 64) float mul;
} data;

View file

@ -0,0 +1,20 @@
#version 450
#include "../frag_spec_const.glsl"
#include "tex.common.glsl"
layout(set = 0, binding = 0) uniform sampler2D tex;
layout(location = 0) in vec2 tex_pos;
layout(location = 0) out vec4 out_color;
void main() {
vec4 c = textureLod(tex, tex_pos, 0);
if (has_alpha_multiplier) {
if (src_has_alpha) {
c *= data.mul;
} else {
c = vec4(c.rgb * data.mul, data.mul);
}
}
out_color = c;
}

View file

@ -0,0 +1,18 @@
#version 450
//#extension GL_EXT_debug_printf : enable
#include "tex.common.glsl"
layout(location = 0) out vec2 tex_pos;
void main() {
vec2 pos;
switch (gl_VertexIndex) {
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break;
case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break;
case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break;
case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break;
}
gl_Position = vec4(pos, 0.0, 1.0);
// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos);
}

View file

@ -1,3 +1,9 @@
#extension GL_EXT_buffer_reference : require
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
vec2 pos[][4];
};
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
Vertices vertices;
} data;

View file

@ -10,8 +10,11 @@ layout(location = 0) out vec4 out_color;
void main() {
vec4 c = texelFetch(in_color, ivec2(gl_FragCoord.xy), 0);
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
c.rgb = oetf_srgb(c.rgb);
c.rgb *= c.a;
if (eotf != oetf) {
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
c.rgb = apply_eotf(c.rgb);
c.rgb = apply_oetf(c.rgb);
c.rgb *= c.a;
}
out_color = c;
}

View file

@ -1,16 +1,8 @@
#version 450
//#extension GL_EXT_debug_printf : enable
#include "out.common.glsl"
void main() {
vec2 pos;
switch (gl_VertexIndex) {
case 0: pos = data.pos[0]; break;
case 1: pos = data.pos[1]; break;
case 2: pos = data.pos[2]; break;
case 3: pos = data.pos[3]; break;
}
vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
gl_Position = vec4(pos, 0.0, 1.0);
// debugPrintfEXT("X gl_Position = %v4f, pos = %v2f", gl_Position, pos);
}

View file

@ -1,5 +1,15 @@
#extension GL_EXT_buffer_reference : require
struct Vertex {
vec2 pos[4];
vec2 tex_pos[4];
};
layout(buffer_reference, buffer_reference_align = 8, std430) buffer Vertices {
Vertex vertices[];
};
layout(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4];
layout(offset = 32) vec2 tex_pos[4];
layout(offset = 64) float mul;
Vertices vertices;
float mul;
} data;

View file

@ -10,11 +10,12 @@ layout(location = 0) out vec4 out_color;
void main() {
vec4 c = textureLod(tex, tex_pos, 0);
if (color_management) {
if (eotf != oetf) {
if (src_has_alpha) {
c.rgb /= mix(c.a, 1.0, c.a == 0.0);
}
c.rgb = eotf_srgb(c.rgb);
c.rgb = apply_eotf(c.rgb);
c.rgb = apply_oetf(c.rgb);
if (src_has_alpha) {
c.rgb *= c.a;
}

View file

@ -1,18 +1,11 @@
#version 450
//#extension GL_EXT_debug_printf : enable
#include "tex.common.glsl"
layout(location = 0) out vec2 tex_pos;
void main() {
vec2 pos;
switch (gl_VertexIndex) {
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break;
case 1: pos = data.pos[1]; tex_pos = data.tex_pos[1]; break;
case 2: pos = data.pos[2]; tex_pos = data.tex_pos[2]; break;
case 3: pos = data.pos[3]; tex_pos = data.tex_pos[3]; break;
}
gl_Position = vec4(pos, 0.0, 1.0);
// debugPrintfEXT("gl_Position = %v4f, tex_pos = %v2f", gl_Position, tex_pos);
Vertex vertex = data.vertices.vertices[gl_InstanceIndex];
gl_Position = vec4(vertex.pos[gl_VertexIndex], 0.0, 1.0);
tex_pos = vertex.tex_pos[gl_VertexIndex];
}

View file

@ -1,6 +1,11 @@
#ifndef TRANSFER_FUNCTIONS_GLSL
#define TRANSFER_FUNCTIONS_GLSL
#include "frag_spec_const.glsl"
#define SRGB 0
#define LINEAR 1
vec3 eotf_srgb(vec3 c) {
return mix(
c * vec3(1.0 / 12.92),
@ -18,4 +23,20 @@ vec3 oetf_srgb(vec3 c) {
);
}
vec3 apply_eotf(vec3 c) {
switch (eotf) {
case SRGB: return eotf_srgb(c);
case LINEAR: return c;
default: return c;
}
}
vec3 apply_oetf(vec3 c) {
switch (oetf) {
case SRGB: return oetf_srgb(c);
case LINEAR: return c;
default: return c;
}
}
#endif

View file

@ -77,6 +77,9 @@ impl VulkanShmImage {
callback: Rc<dyn AsyncShmGfxTextureCallback>,
tt: TransferType,
) -> Result<Option<PendingShmTransfer>, VulkanError> {
if damage.is_empty() {
return Ok(None);
}
let data = self.async_data.as_ref().unwrap();
let res = self.try_async_transfer(img, staging, data, client_mem, damage, tt);
match res {

View file

@ -0,0 +1,2 @@
pub const TF_SRGB: u32 = 0;
pub const TF_LINEAR: u32 = 1;

View file

@ -4,7 +4,7 @@ use {
globals::{Global, GlobalName},
leaks::Tracker,
object::{Object, Version},
theme::Color,
theme::{Color, TransferFunction},
wire::{
JayCompositorId,
jay_damage_tracking::{
@ -96,12 +96,8 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
req: SetVisualizerColor,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
self.client.state.damage_visualizer.set_color(Color {
r: req.r,
g: req.g,
b: req.b,
a: req.a,
});
let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a;
self.client.state.damage_visualizer.set_color(color);
Ok(())
}

View file

@ -8,7 +8,6 @@ use {
leaks::Tracker,
object::{Object, Version},
rect::{Rect, Region},
theme::Color,
utils::errorfmt::ErrorFmt,
video::dmabuf::DmaBuf,
wire::{WlBufferId, wl_buffer::*},
@ -42,7 +41,7 @@ pub struct WlBuffer {
render_ctx_version: Cell<u32>,
pub storage: RefCell<Option<WlBufferStorage>>,
shm: bool,
pub color: Option<Color>,
pub color: Option<[u32; 4]>,
width: i32,
height: i32,
pub tracker: Tracker<Self>,
@ -149,7 +148,7 @@ impl WlBuffer {
width: 1,
height: 1,
tracker: Default::default(),
color: Some(Color::from_u32_rgba_premultiplied(r, g, b, a)),
color: Some([r, g, b, a]),
}
}

View file

@ -435,7 +435,7 @@ impl GfxFramebuffer for TestGfxFb {
a = 255;
}
staging[(y * width + x) as usize] =
Color::from_rgba_premultiplied(r, g, b, a);
Color::from_srgba_premultiplied(r, g, b, a);
}
data = data.add(stride as usize);
}
@ -446,7 +446,7 @@ impl GfxFramebuffer for TestGfxFb {
for y in 0..height {
for x in 0..width {
let [r, g, b, a] =
staging[(y * width + x) as usize].to_rgba_premultiplied();
staging[(y * width + x) as usize].to_srgba_premultiplied();
*data.add((x * 4) as usize).cast::<[u8; 4]>() = [b, g, r, a];
}
data = data.add(stride as usize);
@ -499,7 +499,7 @@ impl GfxFramebuffer for TestGfxFb {
if !t_format.has_alpha {
a = 255;
}
let mut color = Color::from_rgba_premultiplied(r, g, b, a);
let mut color = Color::from_srgba_premultiplied(r, g, b, a);
if let Some(alpha) = c.alpha {
color = color * alpha;
}

View file

@ -19,7 +19,7 @@ pub struct TestShmBuffer {
impl TestShmBuffer {
pub fn fill(&self, color: Color) {
let [cr, cg, cb, ca] = color.to_rgba_premultiplied();
let [cr, cg, cb, ca] = color.to_srgba_premultiplied();
for [b, g, r, a] in self.deref().array_chunks_ext::<4>() {
r.set(cr);
g.set(cg);

View file

@ -4,7 +4,7 @@ use {
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
test_transport::TestTransport,
},
theme::Color,
theme::{Color, TransferFunction},
wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*},
},
std::{cell::Cell, rc::Rc},
@ -31,13 +31,14 @@ impl TestSinglePixelBufferManager {
destroyed: Cell::new(false),
});
let map = |c: f32| (c as f64 * u32::MAX as f64) as u32;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
self.tran.send(CreateU32RgbaBuffer {
self_id: self.id,
id: obj.id,
r: map(color.r),
g: map(color.g),
b: map(color.b),
a: map(color.a),
r: map(r),
g: map(g),
b: map(b),
a: map(a),
})?;
self.tran.add_obj(obj.clone())?;
Ok(obj)

View file

@ -39,6 +39,6 @@ impl TestSurfaceExt {
}
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) {
self.color.set(Color::from_rgba_straight(r, g, b, a));
self.color.set(Color::from_srgba_straight(r, g, b, a));
}
}

View file

@ -34,7 +34,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let buffer = client
.spbm
.create_buffer(Color::from_rgba_straight(255, 255, 255, 255))?;
.create_buffer(Color::from_srgba_straight(255, 255, 255, 255))?;
child.attach(buffer.id)?;
child_viewport.set_source(0, 0, 1, 1)?;

View file

@ -25,7 +25,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let win1 = client.create_window().await?;
win1.map2().await?;
let buffer = client.spbm.create_buffer(Color::from_rgb(255, 0, 0))?;
let buffer = client.spbm.create_buffer(Color::from_srgb(255, 0, 0))?;
let surface = client.comp.create_surface().await?;
let vp = client.viewporter.get_viewport(&surface)?;
vp.set_destination(100, 100)?;

View file

@ -36,11 +36,11 @@ async fn test(run: Rc<TestRun>) -> TestResult {
}};
}
let buf1 = client.spbm.create_buffer(Color::from_rgb(0, 255, 0))?;
let buf1 = client.spbm.create_buffer(Color::from_srgb(0, 255, 0))?;
let (ss1, alpha1) = create_surface!(&buf1, 0, 0);
let buf2 = client.shm.create_buffer(1, 1)?;
buf2.fill(Color::from_rgb(0, 255, 0));
buf2.fill(Color::from_srgb(0, 255, 0));
let (ss2, alpha2) = create_surface!(&buf2.buffer, 100, 0);
client.compare_screenshot("1", false).await?;

View file

@ -67,18 +67,18 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [&accept_button, &reject_button] {
button.border_color.set(Color::from_gray(100));
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
accept_button.bg_color.set(Color::from_srgb(170, 200, 170));
accept_button
.bg_hover_color
.set(Color::from_rgb(170, 255, 170));
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
.set(Color::from_srgb(170, 255, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_rgb(255, 170, 170));
.set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);

View file

@ -87,22 +87,22 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>, for_restore: bool) -> Rc
&window_button,
&reject_button,
] {
button.border_color.set(Color::from_gray(100));
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
restore_button.bg_color.set(Color::from_rgb(170, 170, 200));
restore_button.bg_color.set(Color::from_srgb(170, 170, 200));
restore_button
.bg_hover_color
.set(Color::from_rgb(170, 170, 255));
.set(Color::from_srgb(170, 170, 255));
for button in [&accept_button, &workspace_button, &window_button] {
button.bg_color.set(Color::from_rgb(170, 200, 170));
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
button.bg_color.set(Color::from_srgb(170, 200, 170));
button.bg_hover_color.set(Color::from_srgb(170, 255, 170));
}
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_rgb(255, 170, 170));
.set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);

View file

@ -7,7 +7,7 @@ use {
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
},
rect::Rect,
theme::Color,
theme::{Color, TransferFunction},
},
std::{ops::Neg, rc::Rc, sync::Arc},
};
@ -78,9 +78,9 @@ pub fn render(
let data = create_data(font, width, height, scale)?;
data.layout.set_text(text);
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);
data.cctx.move_to(x as f64, y as f64);
data.layout.show_layout();

View file

@ -139,9 +139,9 @@ impl Default for Button {
hover: Default::default(),
padding: Default::default(),
border: Default::default(),
border_color: Cell::new(Color::from_gray(0)),
bg_color: Cell::new(Color::from_gray(255)),
bg_hover_color: Cell::new(Color::from_gray(255)),
border_color: Cell::new(Color::from_gray_srgb(0)),
bg_color: Cell::new(Color::from_gray_srgb(255)),
bg_hover_color: Cell::new(Color::from_gray_srgb(255)),
text: Default::default(),
font: Arc::new(DEFAULT_FONT.to_string()),
tex: Default::default(),
@ -172,7 +172,7 @@ impl GuiElement for Button {
None,
&self.font,
&text,
Color::from_gray(0),
Color::from_gray_srgb(0),
Some(scale as _),
true,
);
@ -296,7 +296,7 @@ impl GuiElement for Label {
None,
&self.font,
&text,
Color::from_gray(255),
Color::from_gray_srgb(255),
Some(scale as _),
false,
);
@ -635,7 +635,7 @@ impl WindowData {
AcquireSync::Implicit,
ReleaseSync::Implicit,
self.scale.get(),
Some(&Color::from_gray(0)),
Some(&Color::from_gray_srgb(0)),
None,
&mut |r| {
if let Some(content) = self.content.get() {

View file

@ -11,7 +11,7 @@ use {
renderer::renderer_base::RendererBase,
scale::Scale,
state::State,
theme::Color,
theme::{Color, TransferFunction},
tree::{
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
ToplevelNodeBase, WorkspaceNode,
@ -202,7 +202,7 @@ impl Renderer<'_> {
let pos = placeholder.tl_data().pos.get();
self.base.fill_boxes(
std::slice::from_ref(&pos.at_point(x, y)),
&Color::from_rgba_straight(20, 20, 20, 255),
&Color::from_srgba_straight(20, 20, 20, 255),
);
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) {
if let Some(texture) = tex.texture() {
@ -448,8 +448,15 @@ impl Renderer<'_> {
Some(bounds) => rect.intersect(*bounds),
};
if !rect.is_empty() {
let color = Color::from_u32_premultiplied(
TransferFunction::Srgb,
color[0],
color[1],
color[2],
color[3],
);
self.base.ops.push(GfxApiOpt::Sync);
self.base.fill_scaled_boxes(&[rect], color, alpha);
self.base.fill_scaled_boxes(&[rect], &color, alpha);
}
}
} else {

View file

@ -14,7 +14,7 @@ use {
},
},
rect::{Rect, Region},
theme::Color,
theme::{Color, TransferFunction},
utils::{
clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent,
},
@ -187,9 +187,9 @@ fn render(
data.layout.set_text(text);
}
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);
data.cctx.move_to(x as f64, y as f64);
data.layout.show_layout();

View file

@ -3,12 +3,18 @@ use {
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TransferFunction {
Srgb,
Linear,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
r: f32,
g: f32,
b: f32,
a: f32,
}
impl Eq for Color {}
@ -65,77 +71,155 @@ impl Color {
a: 1.0,
};
pub fn from_gray(g: u8) -> Self {
Self::from_rgb(g, g, g)
pub fn new(transfer_function: TransferFunction, mut r: f32, mut g: f32, mut b: f32) -> Self {
fn srgb(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
#[inline(always)]
fn linear(c: f32) -> f32 {
c
}
macro_rules! convert {
($tf:ident) => {{
r = $tf(r);
g = $tf(g);
b = $tf(b);
}};
}
match transfer_function {
TransferFunction::Srgb => convert!(srgb),
TransferFunction::Linear => convert!(linear),
}
Self { r, g, b, a: 1.0 }
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: 1.0,
pub fn new_premultiplied(
transfer_function: TransferFunction,
mut r: f32,
mut g: f32,
mut b: f32,
a: f32,
) -> Self {
if transfer_function == TransferFunction::Linear {
return Self { r, g, b, a };
}
if a < 1.0 && a > 0.0 {
for c in [&mut r, &mut g, &mut b] {
*c /= a;
}
}
let mut c = Self::new(transfer_function, r, g, b);
if a < 1.0 {
c = c * a;
}
c
}
pub fn is_opaque(&self) -> bool {
self.a >= 1.0
}
pub fn from_gray_srgb(g: u8) -> Self {
Self::from_srgb(g, g, g)
}
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: to_f32(a),
}
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new_premultiplied(
TransferFunction::Srgb,
to_f32(r),
to_f32(g),
to_f32(b),
to_f32(a),
)
}
pub fn from_u32_rgba_premultiplied(r: u32, g: u32, b: u32, a: u32) -> Self {
pub fn from_u32_premultiplied(
transfer_function: TransferFunction,
r: u32,
g: u32,
b: u32,
a: u32,
) -> Self {
fn to_f32(c: u32) -> f32 {
((c as f64) / (u32::MAX as f64)) as f32
}
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: to_f32(a),
}
Self::new_premultiplied(
transfer_function,
to_f32(r),
to_f32(g),
to_f32(b),
to_f32(a),
)
}
pub fn from_rgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
let alpha = to_f32(a);
Self {
r: to_f32(r) * alpha,
g: to_f32(g) * alpha,
b: to_f32(b) * alpha,
a: alpha,
pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
let mut c = Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b));
if a < 255 {
c = c * to_f32(a);
}
c
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn to_rgba_premultiplied(self) -> [u8; 4] {
[to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)]
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
}
pub fn to_array_srgb(self, alpha: Option<f32>) -> [f32; 4] {
let a = alpha.unwrap_or(1.0);
[self.r * a, self.g * a, self.b * a, self.a * a]
pub fn to_array(self, transfer_function: TransferFunction) -> [f32; 4] {
self.to_array2(transfer_function, None)
}
pub fn to_array_linear(self, alpha: Option<f32>) -> [f32; 4] {
fn to_linear(srgb: f32) -> f32 {
if srgb <= 0.04045 {
srgb / 12.92
pub fn to_array2(self, transfer_function: TransferFunction, alpha: Option<f32>) -> [f32; 4] {
let mut res = [self.r, self.g, self.b, self.a];
fn srgb(c: f32) -> f32 {
if c <= 0.0031308 {
c * 12.92
} else {
((srgb + 0.055) / 1.055).powf(2.4)
1.055 * c.powf(1.0 / 2.4) - 0.055
}
}
let a1 = if self.a == 0.0 { 1.0 } else { self.a };
let a2 = self.a * alpha.unwrap_or(1.0);
[
to_linear(self.r / a1) * a2,
to_linear(self.g / a1) * a2,
to_linear(self.b / a1) * a2,
a2,
]
fn linear(c: f32) -> f32 {
c
}
macro_rules! convert {
($tf:ident) => {{
for c in &mut res[..3] {
*c = $tf(*c);
}
}};
}
if transfer_function != TransferFunction::Linear {
if self.a < 1.0 && self.a > 0.0 {
for c in &mut res[..3] {
*c /= self.a;
}
}
match transfer_function {
TransferFunction::Srgb => convert!(srgb),
TransferFunction::Linear => convert!(linear),
}
if self.a < 1.0 {
for c in &mut res[..3] {
*c *= self.a;
}
}
}
if let Some(a) = alpha {
for c in &mut res {
*c *= a;
}
}
res
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
@ -152,7 +236,7 @@ impl Color {
impl From<jay_config::theme::Color> for Color {
fn from(f: jay_config::theme::Color) -> Self {
let [r, g, b, a] = f.to_f32_premultiplied();
Self { r, g, b, a }
Self::new_premultiplied(TransferFunction::Srgb, r, g, b, a)
}
}
@ -184,10 +268,10 @@ macro_rules! colors {
}
};
(@colors ($r:expr, $g:expr, $b:expr)) => {
Color::from_rgb($r, $g, $b)
Color::from_srgb($r, $g, $b)
};
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
Color::from_rgba_straight($r, $g, $b, $a)
Color::from_srgba_straight($r, $g, $b, $a)
};
}