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

View file

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

View file

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

View file

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

View file

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

View file

@ -84,7 +84,7 @@ use {
GL_TRIANGLE_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLES,
}, },
}, },
theme::Color, theme::{Color, TransferFunction},
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage}, utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
video::{ video::{
dmabuf::DMA_BUF_SYNC_READ, 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) { 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; let gles = ctx.ctx.dpy.gles;
unsafe { unsafe {
(gles.glUseProgram)(ctx.fill_prog.prog); (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)( (gles.glVertexAttribPointer)(
ctx.fill_prog_pos as _, ctx.fill_prog_pos as _,
2, 2,

View file

@ -18,7 +18,7 @@ use {
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
}, },
rect::Region, rect::Region,
theme::Color, theme::{Color, TransferFunction},
}, },
std::{ std::{
cell::Cell, cell::Cell,
@ -78,7 +78,8 @@ impl Framebuffer {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo); (gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(gles.glViewport)(0, 0, self.gl.width, self.gl.height); (gles.glViewport)(0, 0, self.gl.width, self.gl.height);
if let Some(c) = clear { 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.glClear)(GL_COLOR_BUFFER_BIT);
} }
(gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); (gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

View file

@ -1,6 +1,7 @@
mod allocator; mod allocator;
mod blend_buffer; mod blend_buffer;
mod bo_allocator; mod bo_allocator;
mod buffer_cache;
mod command; mod command;
mod descriptor; mod descriptor;
mod descriptor_buffer; mod descriptor_buffer;
@ -17,6 +18,7 @@ mod shaders;
mod shm_image; mod shm_image;
mod staging; mod staging;
mod transfer; mod transfer;
mod transfer_functions;
use { use {
crate::{ 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 { use {
crate::{ crate::gfx_apis::vulkan::descriptor::VulkanDescriptorSetLayout, ash::vk::DeviceSize,
gfx_apis::vulkan::{ std::ops::Deref,
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},
}; };
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)] #[derive(Default)]
pub struct VulkanDescriptorBufferWriter { pub struct VulkanDescriptorBufferWriter {
buffer: Vec<u8>, buffer: Vec<u8>,
@ -45,124 +12,6 @@ pub struct VulkanDescriptorBufferSetWriter<'a> {
set: &'a mut [u8], 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 { impl VulkanDescriptorBufferWriter {
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.buffer.clear(); self.buffer.clear();

View file

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

View file

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

View file

@ -1,6 +1,6 @@
use { use {
crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice}, crate::gfx_apis::vulkan::{VulkanError, device::VulkanDevice},
ash::vk::{ShaderModule, ShaderModuleCreateInfo}, ash::vk::{DeviceAddress, ShaderModule, ShaderModuleCreateInfo},
std::rc::Rc, std::rc::Rc,
uapi::Packed, 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 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_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 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 struct VulkanShader {
pub(super) device: Rc<VulkanDevice>, pub(super) device: Rc<VulkanDevice>,
@ -20,17 +26,36 @@ pub struct VulkanShader {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub struct FillPushConstants { pub struct FillPushConstants {
pub pos: [[f32; 2]; 4],
pub color: [f32; 4], pub color: [f32; 4],
pub vertices: DeviceAddress,
pub _padding1: u32,
pub _padding2: u32,
} }
unsafe impl Packed for FillPushConstants {} unsafe impl Packed for FillPushConstants {}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(C)] #[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 pos: [[f32; 2]; 4],
pub tex_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, pub alpha: f32,
} }
@ -38,8 +63,18 @@ unsafe impl Packed for TexPushConstants {}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub struct OutPushConstants { pub struct LegacyTexPushConstants {
pub pos: [[f32; 2]; 4], 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 {} 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(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4]; vec4 color;
layout(offset = 32) vec4 color; Vertices vertices;
uint padding1;
uint padding2;
} data; } data;

View file

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

View file

@ -1,16 +1,8 @@
#version 450 #version 450
//#extension GL_EXT_debug_printf : enable
#include "fill.common.glsl" #include "fill.common.glsl"
void main() { void main() {
vec2 pos; vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
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); 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 = 0) const bool src_has_alpha = false;
layout(constant_id = 1) const bool has_alpha_multiplier = 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 #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(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4]; Vertices vertices;
} data; } data;

View file

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

View file

@ -1,16 +1,8 @@
#version 450 #version 450
//#extension GL_EXT_debug_printf : enable
#include "out.common.glsl" #include "out.common.glsl"
void main() { void main() {
vec2 pos; vec2 pos = data.vertices.pos[gl_InstanceIndex][gl_VertexIndex];
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); 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(push_constant, std430) uniform Data {
layout(offset = 0) vec2 pos[4]; Vertices vertices;
layout(offset = 32) vec2 tex_pos[4]; float mul;
layout(offset = 64) float mul;
} data; } data;

View file

@ -10,11 +10,12 @@ layout(location = 0) out vec4 out_color;
void main() { void main() {
vec4 c = textureLod(tex, tex_pos, 0); vec4 c = textureLod(tex, tex_pos, 0);
if (color_management) { if (eotf != oetf) {
if (src_has_alpha) { if (src_has_alpha) {
c.rgb /= mix(c.a, 1.0, c.a == 0.0); 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) { if (src_has_alpha) {
c.rgb *= c.a; c.rgb *= c.a;
} }

View file

@ -1,18 +1,11 @@
#version 450 #version 450
//#extension GL_EXT_debug_printf : enable
#include "tex.common.glsl" #include "tex.common.glsl"
layout(location = 0) out vec2 tex_pos; layout(location = 0) out vec2 tex_pos;
void main() { void main() {
vec2 pos; Vertex vertex = data.vertices.vertices[gl_InstanceIndex];
switch (gl_VertexIndex) { gl_Position = vec4(vertex.pos[gl_VertexIndex], 0.0, 1.0);
case 0: pos = data.pos[0]; tex_pos = data.tex_pos[0]; break; tex_pos = vertex.tex_pos[gl_VertexIndex];
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,6 +1,11 @@
#ifndef TRANSFER_FUNCTIONS_GLSL #ifndef TRANSFER_FUNCTIONS_GLSL
#define TRANSFER_FUNCTIONS_GLSL #define TRANSFER_FUNCTIONS_GLSL
#include "frag_spec_const.glsl"
#define SRGB 0
#define LINEAR 1
vec3 eotf_srgb(vec3 c) { vec3 eotf_srgb(vec3 c) {
return mix( return mix(
c * vec3(1.0 / 12.92), 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 #endif

View file

@ -77,6 +77,9 @@ impl VulkanShmImage {
callback: Rc<dyn AsyncShmGfxTextureCallback>, callback: Rc<dyn AsyncShmGfxTextureCallback>,
tt: TransferType, tt: TransferType,
) -> Result<Option<PendingShmTransfer>, VulkanError> { ) -> Result<Option<PendingShmTransfer>, VulkanError> {
if damage.is_empty() {
return Ok(None);
}
let data = self.async_data.as_ref().unwrap(); let data = self.async_data.as_ref().unwrap();
let res = self.try_async_transfer(img, staging, data, client_mem, damage, tt); let res = self.try_async_transfer(img, staging, data, client_mem, damage, tt);
match res { 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}, globals::{Global, GlobalName},
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
theme::Color, theme::{Color, TransferFunction},
wire::{ wire::{
JayCompositorId, JayCompositorId,
jay_damage_tracking::{ jay_damage_tracking::{
@ -96,12 +96,8 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
req: SetVisualizerColor, req: SetVisualizerColor,
_slf: &Rc<Self>, _slf: &Rc<Self>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
self.client.state.damage_visualizer.set_color(Color { let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a;
r: req.r, self.client.state.damage_visualizer.set_color(color);
g: req.g,
b: req.b,
a: req.a,
});
Ok(()) Ok(())
} }

View file

@ -8,7 +8,6 @@ use {
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
rect::{Rect, Region}, rect::{Rect, Region},
theme::Color,
utils::errorfmt::ErrorFmt, utils::errorfmt::ErrorFmt,
video::dmabuf::DmaBuf, video::dmabuf::DmaBuf,
wire::{WlBufferId, wl_buffer::*}, wire::{WlBufferId, wl_buffer::*},
@ -42,7 +41,7 @@ pub struct WlBuffer {
render_ctx_version: Cell<u32>, render_ctx_version: Cell<u32>,
pub storage: RefCell<Option<WlBufferStorage>>, pub storage: RefCell<Option<WlBufferStorage>>,
shm: bool, shm: bool,
pub color: Option<Color>, pub color: Option<[u32; 4]>,
width: i32, width: i32,
height: i32, height: i32,
pub tracker: Tracker<Self>, pub tracker: Tracker<Self>,
@ -149,7 +148,7 @@ impl WlBuffer {
width: 1, width: 1,
height: 1, height: 1,
tracker: Default::default(), 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; a = 255;
} }
staging[(y * width + x) as usize] = 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); data = data.add(stride as usize);
} }
@ -446,7 +446,7 @@ impl GfxFramebuffer for TestGfxFb {
for y in 0..height { for y in 0..height {
for x in 0..width { for x in 0..width {
let [r, g, b, a] = 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.add((x * 4) as usize).cast::<[u8; 4]>() = [b, g, r, a];
} }
data = data.add(stride as usize); data = data.add(stride as usize);
@ -499,7 +499,7 @@ impl GfxFramebuffer for TestGfxFb {
if !t_format.has_alpha { if !t_format.has_alpha {
a = 255; 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 { if let Some(alpha) = c.alpha {
color = color * alpha; color = color * alpha;
} }

View file

@ -19,7 +19,7 @@ pub struct TestShmBuffer {
impl TestShmBuffer { impl TestShmBuffer {
pub fn fill(&self, color: Color) { 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>() { for [b, g, r, a] in self.deref().array_chunks_ext::<4>() {
r.set(cr); r.set(cr);
g.set(cg); g.set(cg);

View file

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

View file

@ -39,6 +39,6 @@ impl TestSurfaceExt {
} }
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) { 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 let buffer = client
.spbm .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.attach(buffer.id)?;
child_viewport.set_source(0, 0, 1, 1)?; 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?; let win1 = client.create_window().await?;
win1.map2().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 surface = client.comp.create_surface().await?;
let vp = client.viewporter.get_viewport(&surface)?; let vp = client.viewporter.get_viewport(&surface)?;
vp.set_destination(100, 100)?; 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 (ss1, alpha1) = create_surface!(&buf1, 0, 0);
let buf2 = client.shm.create_buffer(1, 1)?; 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); let (ss2, alpha2) = create_surface!(&buf2.buffer, 100, 0);
client.compare_screenshot("1", false).await?; 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 accept_button = static_button(surface, ButtonRole::Accept, "Allow");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject"); let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [&accept_button, &reject_button] { 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.border.set(2.0);
button.padding.set(5.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 accept_button
.bg_hover_color .bg_hover_color
.set(Color::from_rgb(170, 255, 170)); .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 reject_button
.bg_hover_color .bg_hover_color
.set(Color::from_rgb(255, 170, 170)); .set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default()); let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical); flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center); 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, &window_button,
&reject_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.border.set(2.0);
button.padding.set(5.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 restore_button
.bg_hover_color .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] { for button in [&accept_button, &workspace_button, &window_button] {
button.bg_color.set(Color::from_rgb(170, 200, 170)); button.bg_color.set(Color::from_srgb(170, 200, 170));
button.bg_hover_color.set(Color::from_rgb(170, 255, 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 reject_button
.bg_hover_color .bg_hover_color
.set(Color::from_rgb(255, 170, 170)); .set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default()); let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical); flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center); flow.cross_align.set(Align::Center);

View file

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

View file

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

View file

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

View file

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

View file

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