QtQML / Panel de correlación rápida

Hola a todos! Soy el líder del equipo para el desarrollo de aplicaciones de escritorio en la compañía de Rogia Europe. Desarrollamos soluciones de software para la industria del petróleo y el gas.


Dio la casualidad de que nuestro producto estrella StarSteer no tiene un panel de correlación, una herramienta clásica para guías de pozos. La tarea se pospuso por mucho tiempo debido a otras más prioritarias, pero el otoño pasado finalmente pudimos comenzar.


Sin pasar por las preguntas de la base de código heredada, lo mencionaré en el artículo, había una pregunta fundamental: ¿qué tecnología debería usar? Definitivamente necesitábamos OpenGL, que ya se usa en MapView y en la vista 3D basada en OpenSceneGraph, pero es obvio que no está desnudo y con elementos de interfaz gráfica. OSG se cayó = (. Una tecnología que cumple con dos requisitos: gráfico de escena y GUI en OpenGL - Conocía solo uno - Qt QML / Quick. Acerca de lo que obtuvimos y lo que tenemos que compartir - adentro.


Entrada


Comenzamos a desarrollar el producto en el otoño de 2013. Qué conjunto de bibliotecas usar para mí, como fanático de Qt, ni siquiera era una pregunta. En ese momento, puede surgir la pregunta: use la cuarta o quinta versión (aún bastante reciente) de Qt. Hemos elegido el quinto y desde la altura de nuestro vuelo solo puedo decir: ¡gracias a Dios!


Todo el aspecto se desarrolla en QtGui / Widgets. Todas las escenas donde se muestran los gráficos (gamma, porosidad, resistencia, etc.) se realizan en QGraphicsScene / View. Mi consejo es: ¡no uses este paquete para cosas serias! Argumentos: barra de desplazamiento y lógica no desconectada del exterior (está afuera sin las propias ediciones de Qt) para centrar la escena ( qgraphicsview.cpp +458 y superior en el mismo método para horizontal; qgraphicsview.cpp +3816 - qué control sobre la matriz). Si esto no te molesta, usa muchas piezas convenientes de la caja.


¿Qué más no usar? NSIS


Todo fue perfecto, el producto se estaba desarrollando, se estaban realizando tareas, el número de clientes estaba creciendo. Refactorización ... En general, después de un tiempo, QGraphicsScene comenzó a interferir con nosotros, no teníamos prisa por optimizar la representación de gráficos de 40,000 puntos, cuando se activaron las líneas gruesas, todo esto en la CPU fue muy lento.


En el camino, nos cansamos de desarrollar GUY en widgets. Ya sea con las manos completamente en el código, o un poco en el diseñador (de Creator, .ui + .cpp). Quería cosas modernas, como una descripción declarativa de la interfaz gráfica.


Hizo una lista de tecnologías en las que haremos:


  • a la antigua usanza en QGraphicsView / Scene;
  • para cada pista use un QOpenGLWidget desnudo separado;
  • implementar toda la ventana en QOpenGLWidget, pero la GUI por nosotros mismos (o encontrar algo);
  • dos puntos anteriores + OSG respectivamente;
  • Qt QML / Quick,

solo seis puntos; discutido Mi carisma pesaba más y decidí intentar ver cómo se comportaba el prototipo en QML.


Prototipo


Abrí un ejemplo scenegraph \ graph. Mirado y cerrado =). Jugué durante varios días, miré otros ejemplos, pero nada se acercó a mi preciado objetivo.


¿Y qué se necesitaba entonces? Así es como se vería el panel de correlación:



Una estructura bastante simple es una lista de pistas de pozo, dentro de ella pistas para curvas, escalas; Bueno, las pequeñas cosas. Una gran cantidad de texto, en el futuro, algunos controles: botones, listas desplegables, campos de entrada y más.


Miré a sgengine, aprendí a crear dos gráficos de escena y dibujarlos en la ventana designada. Más tarde me di cuenta de que con esta opción, QML / Quick no lo hará, entonces ¿por qué necesito todo esto?


De hecho, no recuerdo qué medicamentos, pero por alguna razón decidí pasar a los conceptos básicos de los gráficos por computadora. Entonces, en las últimas etapas de la rasterización, todas las coordenadas de la escena se traducen a NKU (coordenadas de dispositivo normalizadas; mejor conocido como NDC = coordenadas de dispositivo normalizadas). Sí, escuché que la salida del sombreador de vértices es en realidad un espacio de recorte y después de eso todavía hay una distorsión afinada, pero todo esto es para una representación tridimensional, y en 2D siempre es w = 1 y, por lo tanto, podemos suponer que la salida inmediatamente a NDC.


OK, NDC, ¿qué sigue? Que si el ancho de su ventana es de 800 píxeles, entonces la coordenada NDC del centro del píxel cero es -1; la coordenada central del 799 es 1. En resumen, ndcX = -1 + 2 * i / 799. Ahora imagine que hay un rectángulo de 100 a 300 y quiero dibujar la escena completa no en una ventana completa, sino en ella. Usando este conocimiento fragmentario, contaré ndcX100, ndcX300, luego los arrojaré al sombreador de vértices y allí, después del estándar


gl_Position = matrix * position; 

linealmente "wrap" gl_Position.x en [ndcX100; ndcX300]. Hacemos lo mismo para el componente vertical. Este truco te permitirá generar escenas en cualquier rectángulo seleccionado de la escena. Con este conocimiento, el ejemplo gráfico comenzó a sufrir cambios. Puedes ver la parroquia aquí: gráfico ; toda la sal en shaders/line.vsh .


SceneItem / Scene


Los siguientes tres meses fueron escritos por TK, resultaron 12 hojas de A4 =). Paralelamente, consideramos la arquitectura. Tomaron MVC ... es MVP ... es MVC / MVP jerárquico ... o incluso PAC: todas estas son convenciones, la buena descomposición es importante.


En general, hemos preparado un ejemplo de una escena. Las fuentes están disponibles aquí: SceneSample . Resultó un cierto marco para crear aplicaciones con gráficos en QtQML / Quick. No olvide que este código sigue siendo un ejemplo. Sí, ya está medio listo y parece más o menos ordenado, pero no está listo.


La escena es el jugador principal. Esta clase monitorea sus coordenadas NDC y actualiza las matrices correspondientes. SceneCamera es muy amiga de él. La siguiente entidad que vale la pena mencionar es SceneItem. Es inútil en sí mismo, solo contiene algo de lógica básica; heredarlo, como LineStrip, e implementar lo que necesita. Al mismo tiempo, en updatePaintNode necesita usar derivados de SceneMaterial - FlatColorMaterial como referencia. Las entidades restantes también hacen algo =), todo tipo de manipuladores, herramientas. Muchas de las clases no se lanzan en QML, y no puede prescindir de un C ++ de este tipo; ¿Recuerdas lo que no está listo?


La segunda dificultad es que si queremos usar controles dentro de una nueva escena, no podremos hacerlo. Pensamos, decidimos que no lo necesitábamos y con calma continuamos el desarrollo.


Ventajas del enfoque:


  • todo se dibuja en una columna de la escena;
  • no corregimos Qt: sigue siendo posible agregar controles QML regulares al escenario para que el orden z entre ellos y las curvas (u otro elemento de escena) sea correcto;
  • menor consumo de memoria en comparación con otros enfoques.

Contras


  • urbermachine compleja;
  • se requiere conocimiento de OpenGL y GLSL;
  • Solución semiacabada.

Por supuesto, encontramos algunas dificultades durante el desarrollo. Uno de ellos era


Error con orden z


Cuando intentamos mostrar por primera vez una escena con curvas, vimos esas imágenes:



A primera vista no estaba claro, "¡hicimos todo bien!" Se adivinó vagamente que gl_Position.z era de alguna manera erróneo, pero por qué era difícil entender por qué por la noche. No nos dimos por vencidos: vimos que Qt corrige los sombreadores y agrega el código para cambiar gl_Position.z, pensamos. Después de un tiempo, nos dimos cuenta: confundimos los datos de la matriz por el cambio en z, ¡y Qt les transfiere sus valores! Por lo tanto, el valor item.z de QML se asigna a z desde OpenGL ( SceneMaterial.cpp +20 ):



Error con clip: verdadero


Una vez en un chat, un equipo de negocios envía una pantalla donde desapareció la línea izquierda de la cuadrícula de coordenadas.



Nuestras preguntas y respuestas atormentaron el programa y encontraron pasos para una reproducción estable: configuramos la escala del monitor para que no sea un múltiplo del 100% y las líneas "parpadean". Artyom se sentó, pensó y descubrió que cuando clip: true y el elemento es rectangular, se usa glScissor, ¡pero sus argumentos son coordenadas de píxeles enteros! En los elementos de QML son reales y resultó que la rasterización de la línea cayó al píxel siguiente / anterior, y las tijeras se cortaron a la corriente.


width: Math.round(metrics.width + leftPadding + 2 * rightPadding + 0.5) la escena así: width: Math.round(metrics.width + leftPadding + 2 * rightPadding + 0.5) . En consecuencia, el elemento de escena siempre debe tener coordenadas enteras para evitar tales artefactos.


En conclusión, traeré KDPV



¡Gracias a todos por su atención!

Source: https://habr.com/ru/post/480334/


All Articles