mirror of
https://github.com/Strum355/mcshader-lsp.git
synced 2025-07-23 03:05:44 +00:00
wip: restructure
This commit is contained in:
parent
d8d77ac600
commit
786e09bdcf
105 changed files with 5156 additions and 3268 deletions
26
server/opengl/Cargo.toml
Normal file
26
server/opengl/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "opengl"
|
||||
version = "0.9.8"
|
||||
authors = ["Noah Santschi-Cooney <noah@santschi-cooney.ch>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
glutin = "0.28"
|
||||
gl = "0.14"
|
||||
url = "2.2"
|
||||
filesystem = { path = "../filesystem" }
|
||||
graph = { path = "../graph" }
|
||||
tower-lsp = "0.17.0"
|
||||
regex = "1.4"
|
||||
mockall = "0.11"
|
||||
logging = { path = "../logging" }
|
||||
sourcefile = { path = "../sourcefile" }
|
||||
|
||||
[dev-dependencies]
|
||||
# workspace = { path = "../workspace" }
|
||||
logging_macro = { path = "../logging_macro" }
|
||||
tokio = { version = "1.18", features = ["fs"]}
|
||||
trim-margin = "0.1"
|
186
server/opengl/src/diagnostics_parser.rs
Normal file
186
server/opengl/src/diagnostics_parser.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
use std::collections::HashMap;
|
||||
use core::cell::OnceCell;
|
||||
use filesystem::NormalizedPathBuf;
|
||||
use logging::debug;
|
||||
use regex::Regex;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
||||
use url::Url;
|
||||
|
||||
use crate::ShaderValidator;
|
||||
use sourcefile::{SourceMapper, SourceNum};
|
||||
|
||||
pub struct DiagnosticsParser<'a, T: ShaderValidator + ?Sized> {
|
||||
// line_offset: OnceCell<u32>,
|
||||
line_regex: OnceCell<Regex>,
|
||||
vendor_querier: &'a T,
|
||||
}
|
||||
|
||||
impl<'a, T: ShaderValidator + ?Sized> DiagnosticsParser<'a, T> {
|
||||
pub fn new(vendor_querier: &'a T) -> Self {
|
||||
DiagnosticsParser {
|
||||
// line_offset: OnceCell::new(),
|
||||
line_regex: OnceCell::new(),
|
||||
vendor_querier,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_line_regex(&self) -> &Regex {
|
||||
self.line_regex.get_or_init(|| match self.vendor_querier.vendor().as_str() {
|
||||
"NVIDIA Corporation" => {
|
||||
Regex::new(r#"^(?P<filepath>\d+)\((?P<linenum>\d+)\) : (?P<severity>error|warning) [A-C]\d+: (?P<output>.+)"#).unwrap()
|
||||
}
|
||||
_ => Regex::new(
|
||||
r#"^(?P<severity>ERROR|WARNING): (?P<filepath>[^?<>*|"\n]+):(?P<linenum>\d+): (?:'.*' :|[a-z]+\(#\d+\)) +(?P<output>.+)$"#,
|
||||
)
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
// fn get_line_offset(&self) -> u32 {
|
||||
// *self.line_offset.get_or_init(|| match self.vendor_querier.vendor().as_str() {
|
||||
// "ATI Technologies" | "ATI Technologies Inc." | "AMD" => 0,
|
||||
// _ => 1,
|
||||
// })
|
||||
// }
|
||||
|
||||
pub fn parse_diagnostics_output(
|
||||
&self,
|
||||
output: String,
|
||||
uri: &NormalizedPathBuf,
|
||||
source_mapper: &SourceMapper<NormalizedPathBuf>,
|
||||
// graph: &CachedStableGraph<NormalizedPathBuf, IncludeLine>,
|
||||
) -> HashMap<Url, Vec<Diagnostic>> {
|
||||
let output_lines = output.split('\n').collect::<Vec<&str>>();
|
||||
let mut diagnostics: HashMap<Url, Vec<Diagnostic>> = HashMap::with_capacity(output_lines.len());
|
||||
|
||||
debug!("diagnostics regex selected"; "regex" => self.get_line_regex() .as_str());
|
||||
|
||||
for line in output_lines {
|
||||
let diagnostic_capture = match self.get_line_regex().captures(line) {
|
||||
Some(d) => d,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
debug!("found match for output line"; "line" => line, "capture" => format!("{:?}", diagnostic_capture));
|
||||
|
||||
let msg = diagnostic_capture.name("output").unwrap().as_str();
|
||||
|
||||
let line = match diagnostic_capture.name("linenum") {
|
||||
Some(c) => c.as_str().parse::<u32>().unwrap_or(0),
|
||||
None => 0,
|
||||
};
|
||||
|
||||
// TODO: line matching maybe
|
||||
/* let line_text = source_lines[line as usize];
|
||||
let leading_whitespace = line_text.len() - line_text.trim_start().len(); */
|
||||
|
||||
let severity = match diagnostic_capture.name("severity") {
|
||||
Some(c) => match c.as_str().to_lowercase().as_str() {
|
||||
"error" => DiagnosticSeverity::ERROR,
|
||||
"warning" => DiagnosticSeverity::WARNING,
|
||||
_ => DiagnosticSeverity::INFORMATION,
|
||||
},
|
||||
_ => DiagnosticSeverity::INFORMATION,
|
||||
};
|
||||
|
||||
let origin = match diagnostic_capture.name("filepath") {
|
||||
Some(o) => {
|
||||
let source_num: SourceNum = o.as_str().parse::<usize>().unwrap().into();
|
||||
source_mapper.get_node(source_num)
|
||||
}
|
||||
None => uri,
|
||||
};
|
||||
|
||||
let diagnostic = Diagnostic {
|
||||
range: Range::new(
|
||||
/* Position::new(line, leading_whitespace as u64),
|
||||
Position::new(line, line_text.len() as u64) */
|
||||
Position::new(line-1, 0),
|
||||
Position::new(line-1, 1000),
|
||||
),
|
||||
severity: Some(severity),
|
||||
source: Some("mcglsl".to_string()),
|
||||
message: msg.trim().into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let origin_url = Url::from_file_path(origin).unwrap();
|
||||
match diagnostics.get_mut(&origin_url) {
|
||||
Some(d) => d.push(diagnostic),
|
||||
None => {
|
||||
diagnostics.insert(origin_url, vec![diagnostic]);
|
||||
}
|
||||
};
|
||||
}
|
||||
diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod diagnostics_test {
|
||||
use filesystem::NormalizedPathBuf;
|
||||
use sourcefile::SourceMapper;
|
||||
use trim_margin::MarginTrimmable;
|
||||
use url::Url;
|
||||
|
||||
use crate::{diagnostics_parser::DiagnosticsParser, MockShaderValidator};
|
||||
|
||||
#[test]
|
||||
#[logging_macro::scope]
|
||||
fn test_nvidia_diagnostics() {
|
||||
logging::scope(&logging::logger().new(slog_o!("driver" => "nvidia")), || {
|
||||
let mut mockgl = MockShaderValidator::new();
|
||||
mockgl.expect_vendor().returning(|| "NVIDIA Corporation".into());
|
||||
|
||||
let output = "0(9) : error C0000: syntax error, unexpected '}', expecting ',' or ';' at token \"}\"";
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
let path: NormalizedPathBuf = "/home/noah/.minecraft/shaderpacks/test/shaders/final.fsh".into();
|
||||
#[cfg(target_family = "windows")]
|
||||
let path: NormalizedPathBuf = "c:\\home\\noah\\.minecraft\\shaderpacks\\test\\shaders\\final.fsh".into();
|
||||
|
||||
let mut source_mapper = SourceMapper::new(0);
|
||||
source_mapper.get_num(&path);
|
||||
|
||||
let parser = DiagnosticsParser::new(&mockgl);
|
||||
|
||||
let results = parser.parse_diagnostics_output(output.to_string(), &path.parent().unwrap(), &source_mapper);
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
let first = results.into_iter().next().unwrap();
|
||||
assert_eq!(first.0, Url::from_file_path(path).unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[logging_macro::scope]
|
||||
fn test_amd_diagnostics() {
|
||||
logging::scope(&logging::logger().new(slog_o!("driver" => "amd")), || {
|
||||
let mut mockgl = MockShaderValidator::new();
|
||||
mockgl.expect_vendor().returning(|| "ATI Technologies".into());
|
||||
|
||||
let output = r#"
|
||||
|ERROR: 0:1: '' : syntax error: #line
|
||||
|ERROR: 0:10: '' : syntax error: #line
|
||||
|ERROR: 0:15: 'varying' : syntax error: syntax error
|
||||
"#.trim_margin().unwrap();
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
let path: NormalizedPathBuf = "/home/noah/.minecraft/shaderpacks/test/shaders/final.fsh".into();
|
||||
#[cfg(target_family = "windows")]
|
||||
let path: NormalizedPathBuf = "c:\\home\\noah\\.minecraft\\shaderpacks\\test\\shaders\\final.fsh".into();
|
||||
|
||||
let mut source_mapper = SourceMapper::new(0);
|
||||
source_mapper.get_num(&path);
|
||||
|
||||
let parser = DiagnosticsParser::new(&mockgl);
|
||||
|
||||
let results = parser.parse_diagnostics_output(output, &path.parent().unwrap(), &source_mapper);
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
let first = results.into_iter().next().unwrap();
|
||||
assert_eq!(first.1.len(), 3);
|
||||
});
|
||||
}
|
||||
}
|
56
server/opengl/src/lib.rs
Normal file
56
server/opengl/src/lib.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
#![feature(once_cell)]
|
||||
mod opengl_context;
|
||||
mod opengl_context_facade;
|
||||
|
||||
use mockall::automock;
|
||||
use opengl_context::*;
|
||||
pub use opengl_context_facade::*;
|
||||
|
||||
pub mod diagnostics_parser;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[automock]
|
||||
pub trait ShaderValidator {
|
||||
fn validate(&self, tree_type: TreeType, source: &str) -> Option<String>;
|
||||
fn vendor(&self) -> String;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GPUVendor {
|
||||
NVIDIA, AMD, OTHER // and thats it folks
|
||||
}
|
||||
|
||||
impl From<&str> for GPUVendor {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"NVIDIA Corporation" => Self::NVIDIA,
|
||||
"AMD" | "ATI Technologies" | "ATI Technologies Inc." => Self::AMD,
|
||||
_ => Self::OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TreeType {
|
||||
Fragment,
|
||||
Vertex,
|
||||
Geometry,
|
||||
Compute,
|
||||
}
|
||||
|
||||
impl From<&str> for TreeType {
|
||||
fn from(ext: &str) -> Self {
|
||||
if ext == "fsh" {
|
||||
TreeType::Fragment
|
||||
} else if ext == "vsh" {
|
||||
TreeType::Vertex
|
||||
} else if ext == "gsh" {
|
||||
TreeType::Geometry
|
||||
} else if ext == "csh" {
|
||||
TreeType::Compute
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
96
server/opengl/src/opengl_context.rs
Normal file
96
server/opengl/src/opengl_context.rs
Normal file
|
@ -0,0 +1,96 @@
|
|||
use std::ffi::{CStr, CString};
|
||||
use std::ptr;
|
||||
|
||||
use glutin::platform::unix::EventLoopExtUnix;
|
||||
use logging::info;
|
||||
|
||||
use crate::ShaderValidator;
|
||||
|
||||
pub(crate) struct Context {
|
||||
_ctx: glutin::Context<glutin::PossiblyCurrent>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn default() -> Context {
|
||||
let events_loop = glutin::event_loop::EventLoop::<()>::new_any_thread();
|
||||
let gl_window = glutin::ContextBuilder::new()
|
||||
.build_headless(&*events_loop, glutin::dpi::PhysicalSize::new(1, 1))
|
||||
.unwrap();
|
||||
|
||||
let gl_window = unsafe {
|
||||
let gl_window = gl_window.make_current().unwrap();
|
||||
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
|
||||
gl_window
|
||||
};
|
||||
|
||||
let gl_ctx = Context { _ctx: gl_window };
|
||||
|
||||
unsafe {
|
||||
info!(
|
||||
"OpenGL device";
|
||||
"vendor" => gl_ctx.vendor(),
|
||||
"version" => String::from_utf8(CStr::from_ptr(gl::GetString(gl::VERSION) as *const _).to_bytes().to_vec()).unwrap(),
|
||||
"renderer" => String::from_utf8(CStr::from_ptr(gl::GetString(gl::RENDERER) as *const _).to_bytes().to_vec()).unwrap()
|
||||
);
|
||||
}
|
||||
gl_ctx
|
||||
}
|
||||
|
||||
unsafe fn compile_and_get_shader_log(&self, shader: gl::types::GLuint, source: &str) -> Option<String> {
|
||||
let mut success = i32::from(gl::FALSE);
|
||||
let c_str_frag = CString::new(source).unwrap();
|
||||
gl::ShaderSource(shader, 1, &c_str_frag.as_ptr(), ptr::null());
|
||||
gl::CompileShader(shader);
|
||||
|
||||
// Check for shader compilation errors
|
||||
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
|
||||
let result = if success != i32::from(gl::TRUE) {
|
||||
let mut info_len: gl::types::GLint = 0;
|
||||
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut info_len);
|
||||
let mut info = vec![0u8; info_len as usize];
|
||||
gl::GetShaderInfoLog(
|
||||
shader,
|
||||
info_len as gl::types::GLsizei,
|
||||
ptr::null_mut(),
|
||||
info.as_mut_ptr() as *mut gl::types::GLchar,
|
||||
);
|
||||
info.set_len((info_len - 1) as usize); // ignore null for str::from_utf8
|
||||
Some(String::from_utf8(info).unwrap())
|
||||
} else { None };
|
||||
gl::DeleteShader(shader);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl ShaderValidator for Context {
|
||||
fn validate(&self, tree_type: super::TreeType, source: &str) -> Option<String> {
|
||||
unsafe {
|
||||
match tree_type {
|
||||
crate::TreeType::Fragment => {
|
||||
// Fragment shader
|
||||
let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
|
||||
self.compile_and_get_shader_log(fragment_shader, source)
|
||||
}
|
||||
crate::TreeType::Vertex => {
|
||||
// Vertex shader
|
||||
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
|
||||
self.compile_and_get_shader_log(vertex_shader, source)
|
||||
}
|
||||
crate::TreeType::Geometry => {
|
||||
// Geometry shader
|
||||
let geometry_shader = gl::CreateShader(gl::GEOMETRY_SHADER);
|
||||
self.compile_and_get_shader_log(geometry_shader, source)
|
||||
}
|
||||
crate::TreeType::Compute => {
|
||||
// Compute shader
|
||||
let compute_shader = gl::CreateShader(gl::COMPUTE_SHADER);
|
||||
self.compile_and_get_shader_log(compute_shader, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vendor(&self) -> String {
|
||||
unsafe { String::from_utf8(CStr::from_ptr(gl::GetString(gl::VENDOR) as *const _).to_bytes().to_vec()).unwrap() }
|
||||
}
|
||||
}
|
87
server/opengl/src/opengl_context_facade.rs
Normal file
87
server/opengl/src/opengl_context_facade.rs
Normal file
|
@ -0,0 +1,87 @@
|
|||
use std::{
|
||||
sync::{
|
||||
mpsc::{self, Receiver, SyncSender},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use crate::{Context, ShaderValidator};
|
||||
|
||||
enum ClientMessage {
|
||||
Validate { tree_type: crate::TreeType, source: Arc<str> },
|
||||
Vendor,
|
||||
}
|
||||
|
||||
enum ServerMessage {
|
||||
Validate(Option<String>),
|
||||
Vendor(String),
|
||||
}
|
||||
|
||||
///
|
||||
pub struct ContextFacade {
|
||||
start_chan: SyncSender<()>,
|
||||
client_tx: SyncSender<ClientMessage>,
|
||||
server_rx: Receiver<ServerMessage>,
|
||||
}
|
||||
|
||||
impl ContextFacade {
|
||||
pub fn default() -> Self {
|
||||
let (client_tx, client_rx) = mpsc::sync_channel::<ClientMessage>(1);
|
||||
let (server_tx, server_rx) = mpsc::sync_channel::<ServerMessage>(1);
|
||||
let (start_chan, start_chan_recv) = mpsc::sync_channel::<()>(1);
|
||||
thread::spawn(move || {
|
||||
start_chan_recv.recv().unwrap();
|
||||
|
||||
let opengl_ctx = Context::default();
|
||||
loop {
|
||||
match client_rx.recv() {
|
||||
Ok(msg) => {
|
||||
if let ClientMessage::Validate { tree_type, source } = msg {
|
||||
server_tx
|
||||
.send(ServerMessage::Validate(opengl_ctx.validate(tree_type, &source)))
|
||||
.unwrap();
|
||||
} else {
|
||||
server_tx.send(ServerMessage::Vendor(opengl_ctx.vendor())).unwrap();
|
||||
}
|
||||
}
|
||||
Err(_) => return,
|
||||
}
|
||||
start_chan_recv.recv().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
ContextFacade {
|
||||
start_chan,
|
||||
client_tx,
|
||||
server_rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShaderValidator for ContextFacade {
|
||||
fn validate(&self, tree_type: crate::TreeType, source: &str) -> Option<String> {
|
||||
self.start_chan.send(()).unwrap();
|
||||
match self.client_tx.send(ClientMessage::Validate {
|
||||
tree_type,
|
||||
source: Arc::from(source),
|
||||
}) {
|
||||
Ok(_) => match self.server_rx.recv().unwrap() {
|
||||
ServerMessage::Validate(output) => output,
|
||||
ServerMessage::Vendor(_) => panic!("expected validate reply, got vendor"),
|
||||
},
|
||||
Err(e) => panic!("error sending vendor message: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn vendor(&self) -> String {
|
||||
self.start_chan.send(()).unwrap();
|
||||
match self.client_tx.send(ClientMessage::Vendor) {
|
||||
Ok(_) => match self.server_rx.recv().unwrap() {
|
||||
ServerMessage::Validate(_) => panic!("expected vendor reply, got validate"),
|
||||
ServerMessage::Vendor(resp) => resp,
|
||||
},
|
||||
Err(e) => panic!("error sending vendor message: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue