Новый пример OpenGL
This commit is contained in:
parent
28854f2864
commit
6211fe2275
|
|
@ -1,244 +1,172 @@
|
|||
{$reference System.Windows.Forms.dll}
|
||||
{$reference System.Drawing.dll}
|
||||
|
||||
// Данный пример демонстрирует запуск простейшей программы с модулем OpenGL
|
||||
// Примите во внимание, что методы из gl_gdi. - могут служить только временной заменой в серьёзной программе
|
||||
// (По крайней мере в данной версии. В будущем, возможно, gl_gdi будет улучшено)
|
||||
// Они использованы тут, чтоб пример был проще
|
||||
|
||||
uses System.Windows.Forms;
|
||||
uses System.Drawing;
|
||||
uses System;
|
||||
uses OpenGL;
|
||||
|
||||
{$apptype windows} // убирает консоль
|
||||
|
||||
var gl: OpenGL.gl<PlWin>;
|
||||
|
||||
{$region Shader}
|
||||
|
||||
function InitShader(fname: string; st: ShaderType): gl_shader;
|
||||
begin
|
||||
Result := gl.CreateShader(st);
|
||||
|
||||
var source := ReadAllText(fname);
|
||||
|
||||
gl.ShaderSource(Result, 1,
|
||||
new string[](source),
|
||||
nil
|
||||
);
|
||||
|
||||
gl.CompileShader(Result);
|
||||
// получаем состояние успешности компиляции
|
||||
// 1=успешно
|
||||
// 0=ошибка
|
||||
var comp_ok: integer;
|
||||
gl.GetShaderiv(Result, ShaderParameterName.COMPILE_STATUS, comp_ok);
|
||||
if comp_ok = 0 then
|
||||
begin
|
||||
|
||||
// узнаём нужную длинную строки
|
||||
var l: integer;
|
||||
gl.GetShaderiv(Result, ShaderParameterName.INFO_LOG_LENGTH, l);
|
||||
|
||||
// выделяем достаточно памяти чтоб сохранить строку
|
||||
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
|
||||
|
||||
// получаем строку логов
|
||||
gl.GetShaderInfoLog(Result, l, IntPtr.Zero, ptr);
|
||||
|
||||
// преобразовываем в управляемую строку
|
||||
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
|
||||
Writeln(log);
|
||||
|
||||
// и в конце обязательно освобождаем, чтобы не было утечек памяти
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
|
||||
gl.DeleteShader(Result);
|
||||
|
||||
Result := gl_shader.Zero;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Shader}
|
||||
|
||||
{$region Program}
|
||||
|
||||
function InitProgram(vertex_shader, fragment_shader: gl_shader): gl_program;
|
||||
begin
|
||||
Result := gl.CreateProgram;
|
||||
|
||||
gl.AttachShader(Result, vertex_shader);
|
||||
if fragment_shader<>gl_shader.Zero then gl.AttachShader(Result, fragment_shader);
|
||||
|
||||
gl.LinkProgram(Result);
|
||||
// всё то же самое что и у шейдеров
|
||||
var link_ok: integer;
|
||||
gl.GetProgramiv(Result, ProgramPropertyARB.LINK_STATUS, link_ok);
|
||||
if link_ok = 0 then
|
||||
begin
|
||||
|
||||
var l: integer;
|
||||
gl.GetProgramiv(Result, ProgramPropertyARB.INFO_LOG_LENGTH, l);
|
||||
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
|
||||
|
||||
gl.GetProgramInfoLog(Result, l, IntPtr.Zero, ptr);
|
||||
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
|
||||
Writeln(log);
|
||||
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
|
||||
gl.DeleteProgram(Result);
|
||||
|
||||
Result := gl_program.Zero;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Program}
|
||||
|
||||
const dy = -Sin(Pi / 6) / 2;
|
||||
|
||||
begin
|
||||
|
||||
// Создаёт и настраиваем окно
|
||||
var f := new Form;
|
||||
f.StartPosition := FormStartPosition.CenterScreen;
|
||||
f.ClientSize := new Size(500, 500);
|
||||
f.FormBorderStyle := FormBorderStyle.Fixed3D;
|
||||
// Если окно закрылось - надо сразу завершить программу
|
||||
f.Closed += (o,e)->Halt();
|
||||
|
||||
// Настраиваем поверхность рисования
|
||||
var hdc := gl_gdi.InitControl(f);
|
||||
|
||||
// Настраиваем перерисовку
|
||||
gl_gdi.SetupControlRedrawing(f, hdc, EndFrame->
|
||||
begin
|
||||
|
||||
{$region Настройка глобальных параметров OpenGL}
|
||||
|
||||
// При создании экземпляра OpenGL.gl инициализируются некоторые функции
|
||||
// Это необходимо для всех функций из OpenGL1.2 и выше, потому что они локальны для контекста OpenGL
|
||||
gl := new OpenGL.gl<PlWin>;
|
||||
|
||||
{$endregion Настройка глобальных параметров OpenGL}
|
||||
|
||||
{$region Инициализация переменных}
|
||||
|
||||
var vertex_pos_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, vertex_pos_buffer);
|
||||
gl.NamedBufferData(
|
||||
vertex_pos_buffer,
|
||||
new IntPtr(3*sizeof(Vec2f)),
|
||||
ArrGen(3, i->
|
||||
begin
|
||||
var rot := i * Pi * 2 / 3;
|
||||
|
||||
Result := new Vec2f(
|
||||
Sin(rot),
|
||||
Cos(rot) + dy
|
||||
);
|
||||
|
||||
end),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var vertex_clr_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, vertex_clr_buffer);
|
||||
gl.NamedBufferData(
|
||||
vertex_clr_buffer,
|
||||
new IntPtr(3*sizeof(Vec3f)),
|
||||
new Vec3f[](
|
||||
new Vec3f(1,0,0),
|
||||
new Vec3f(0,1,0),
|
||||
new Vec3f(0,0,1)
|
||||
),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var element_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, element_buffer);
|
||||
gl.NamedBufferData(
|
||||
element_buffer,
|
||||
new IntPtr(3*sizeof(byte)),
|
||||
new byte[](
|
||||
0,1,2
|
||||
),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var vertex_shader := InitShader('Rot Triangle 1.vertex.glsl', ShaderType.VERTEX_SHADER);
|
||||
// var fragment_shader := InitShader('fragment.glsl', ShaderType.FRAGMENT_SHADER);
|
||||
|
||||
var sprog := InitProgram(vertex_shader, gl_shader.Zero {fragment_shader});
|
||||
|
||||
var uniform_rot_k := gl.GetUniformLocation(sprog, 'rot_k');
|
||||
|
||||
var attribute_position := gl.GetAttribLocation(sprog, 'position');
|
||||
var attribute_color := gl.GetAttribLocation(sprog, 'color');
|
||||
|
||||
var t := new Stopwatch;
|
||||
t.Start;
|
||||
|
||||
{$endregion Инициализация переменных}
|
||||
|
||||
while true do
|
||||
begin
|
||||
// очищаем окно в начале перерисовки
|
||||
gl.Clear(ClearBufferMask.COLOR_BUFFER_BIT);
|
||||
|
||||
|
||||
|
||||
gl.UseProgram(sprog);
|
||||
|
||||
gl.Uniform1f(uniform_rot_k, Cos( t.Elapsed.Ticks * 0.0000002 ) );
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_pos_buffer);
|
||||
gl.VertexAttribPointer(
|
||||
attribute_position,
|
||||
2,
|
||||
VertexAttribPointerType.FLOAT,
|
||||
false,
|
||||
8,
|
||||
IntPtr.Zero
|
||||
);
|
||||
gl.EnableVertexAttribArray(attribute_position);
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ARRAY_BUFFER, vertex_clr_buffer);
|
||||
gl.VertexAttribPointer(
|
||||
attribute_color,
|
||||
3,
|
||||
VertexAttribPointerType.FLOAT,
|
||||
false,
|
||||
12,
|
||||
IntPtr.Zero
|
||||
);
|
||||
gl.EnableVertexAttribArray(attribute_color);
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
gl.DrawElements(
|
||||
PrimitiveType.TRIANGLES,
|
||||
3,
|
||||
DrawElementsType.UNSIGNED_BYTE,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
gl.DisableVertexAttribArray(attribute_position);
|
||||
gl.DisableVertexAttribArray(attribute_color);
|
||||
|
||||
|
||||
|
||||
// получаем тип последней ошибки
|
||||
var err := gl.GetError;
|
||||
// и если ошибка есть - выводим её
|
||||
if err<>ErrorCode.NO_ERROR then Writeln(err);
|
||||
|
||||
gl.Finish;
|
||||
// EndFrame меняет местами буферы и ждёт vsync
|
||||
EndFrame;
|
||||
end;
|
||||
|
||||
end);
|
||||
|
||||
Application.Run(f);
|
||||
{$reference System.Windows.Forms.dll}
|
||||
{$reference System.Drawing.dll}
|
||||
|
||||
// Данный пример демонстрирует запуск простейшей программы с модулем OpenGL
|
||||
// Примите во внимание, что методы из gl_gdi. - могут служить только временной заменой в серьёзной программе
|
||||
// (По крайней мере в данной версии. В будущем, возможно, gl_gdi будет улучшено)
|
||||
// Они использованы тут, чтоб пример был проще
|
||||
|
||||
uses System.Windows.Forms;
|
||||
uses System;
|
||||
uses OpenGL;
|
||||
|
||||
uses Common in '..\Common';
|
||||
|
||||
{$apptype windows} // убираем консоль
|
||||
|
||||
const dy = -Sin(Pi / 6) / 2;
|
||||
|
||||
begin
|
||||
|
||||
// Создаём и настраиваем окно
|
||||
var f := new Form;
|
||||
f.StartPosition := FormStartPosition.CenterScreen;
|
||||
f.ClientSize := new System.Drawing.Size(500, 500);
|
||||
f.FormBorderStyle := FormBorderStyle.Fixed3D;
|
||||
// Если окно закрылось - надо сразу завершить программу
|
||||
f.Closed += (o,e)->Halt();
|
||||
|
||||
// Настраиваем поверхность рисования
|
||||
var hdc := gl_gdi.InitControl(f);
|
||||
|
||||
// Настраиваем перерисовку
|
||||
gl_gdi.SetupControlRedrawing(f, hdc, EndFrame->
|
||||
begin
|
||||
|
||||
{$region Настройка глобальных параметров OpenGL}
|
||||
|
||||
// При создании экземпляра OpenGL.gl инициализируются некоторые функции
|
||||
// Это необходимо для всех функций из OpenGL1.2 и выше, потому что они локальны для контекста OpenGL
|
||||
gl := new OpenGL.gl<PlWin>;
|
||||
|
||||
{$endregion Настройка глобальных параметров OpenGL}
|
||||
|
||||
{$region Инициализация переменных}
|
||||
|
||||
var vertex_pos_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, vertex_pos_buffer);
|
||||
gl.NamedBufferData(
|
||||
vertex_pos_buffer,
|
||||
new IntPtr(3*sizeof(Vec2f)),
|
||||
ArrGen(3, i->
|
||||
begin
|
||||
var rot := i * Pi * 2 / 3;
|
||||
|
||||
Result := new Vec2f(
|
||||
Sin(rot),
|
||||
Cos(rot) + dy
|
||||
);
|
||||
|
||||
end),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var vertex_clr_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, vertex_clr_buffer);
|
||||
gl.NamedBufferData(
|
||||
vertex_clr_buffer,
|
||||
new IntPtr(3*sizeof(Vec3f)),
|
||||
new Vec3f[](
|
||||
new Vec3f(1,0,0),
|
||||
new Vec3f(0,1,0),
|
||||
new Vec3f(0,0,1)
|
||||
),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var element_buffer: gl_buffer;
|
||||
gl.CreateBuffers(1, element_buffer);
|
||||
gl.NamedBufferData(
|
||||
element_buffer,
|
||||
new IntPtr(3*sizeof(byte)),
|
||||
new byte[](
|
||||
0,1,2
|
||||
),
|
||||
VertexBufferObjectUsage.STATIC_DRAW
|
||||
);
|
||||
|
||||
var vertex_shader := InitShader('Крутящийся треугольник.vert', ShaderType.VERTEX_SHADER);
|
||||
|
||||
var sprog := InitProgram(vertex_shader{, другие_шейдеры});
|
||||
|
||||
var uniform_rot_k := gl.GetUniformLocation(sprog, 'rot_k');
|
||||
|
||||
var attribute_position := gl.GetAttribLocation(sprog, 'position');
|
||||
var attribute_color := gl.GetAttribLocation(sprog, 'color');
|
||||
|
||||
var t := new Stopwatch;
|
||||
t.Start;
|
||||
|
||||
{$endregion Инициализация переменных}
|
||||
|
||||
// Для поиска, где возникла ошибка
|
||||
// var RaiseIfError := procedure->
|
||||
// begin
|
||||
//
|
||||
// // получаем тип последней ошибки
|
||||
// var err := gl.GetError;
|
||||
// // и если ошибка есть - выводим её
|
||||
// if err<>ErrorCode.NO_ERROR then raise new System.Exception(err.ToString);
|
||||
//
|
||||
// end;
|
||||
|
||||
while true do
|
||||
begin
|
||||
// очищаем окно в начале перерисовки
|
||||
gl.Clear(ClearBufferMask.COLOR_BUFFER_BIT);
|
||||
|
||||
|
||||
|
||||
gl.UseProgram(sprog);
|
||||
|
||||
gl.Uniform1f(uniform_rot_k, Cos( t.Elapsed.Ticks * 0.0000002 ) );
|
||||
|
||||
gl.BindBuffer(BufferTarget.ARRAY_BUFFER, vertex_pos_buffer);
|
||||
gl.VertexAttribPointer(
|
||||
attribute_position,
|
||||
2,
|
||||
VertexAttribPointerType.FLOAT,
|
||||
false,
|
||||
8,
|
||||
IntPtr.Zero
|
||||
);
|
||||
gl.EnableVertexAttribArray(attribute_position);
|
||||
|
||||
gl.BindBuffer(BufferTarget.ARRAY_BUFFER, vertex_clr_buffer);
|
||||
gl.VertexAttribPointer(
|
||||
attribute_color,
|
||||
3,
|
||||
VertexAttribPointerType.FLOAT,
|
||||
false,
|
||||
12,
|
||||
IntPtr.Zero
|
||||
);
|
||||
gl.EnableVertexAttribArray(attribute_color);
|
||||
|
||||
gl.BindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, element_buffer);
|
||||
gl.DrawElements(
|
||||
PrimitiveType.TRIANGLES,
|
||||
3,
|
||||
DrawElementsType.UNSIGNED_BYTE,
|
||||
IntPtr.Zero
|
||||
);
|
||||
|
||||
gl.DisableVertexAttribArray(attribute_position);
|
||||
gl.DisableVertexAttribArray(attribute_color);
|
||||
|
||||
|
||||
|
||||
// получаем тип последней ошибки
|
||||
var err := gl.GetError;
|
||||
// и если ошибка есть - выводим её
|
||||
if err<>ErrorCode.NO_ERROR then Writeln(err);
|
||||
|
||||
gl.Finish;
|
||||
// EndFrame меняет местами буферы и ждёт vsync
|
||||
EndFrame;
|
||||
end;
|
||||
|
||||
end);
|
||||
|
||||
Application.Run(f);
|
||||
end.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#version 460
|
||||
|
||||
attribute vec2 position;
|
||||
attribute vec3 color;
|
||||
in vec2 position;
|
||||
in vec3 color;
|
||||
uniform float rot_k;
|
||||
|
||||
void main()
|
||||
87
InstallerSamples/OpenGL и OpenCL/OpenGL/Common.pas
Normal file
87
InstallerSamples/OpenGL и OpenCL/OpenGL/Common.pas
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
unit Common;
|
||||
|
||||
uses System;
|
||||
uses OpenGL;
|
||||
|
||||
var gl: OpenGL.gl<PlWin>;
|
||||
|
||||
{$region Shader}
|
||||
|
||||
function InitShader(fname: string; st: ShaderType): gl_shader;
|
||||
begin
|
||||
Result := gl.CreateShader(st);
|
||||
|
||||
var source := ReadAllText(fname);
|
||||
|
||||
gl.ShaderSource(Result, 1,
|
||||
new string[](source),
|
||||
nil
|
||||
);
|
||||
|
||||
gl.CompileShader(Result);
|
||||
// Получаем состояние успешности компиляции
|
||||
// 1=успешно
|
||||
// 0=ошибка
|
||||
var comp_ok: integer;
|
||||
gl.GetShaderiv(Result, ShaderParameterName.COMPILE_STATUS, comp_ok);
|
||||
if comp_ok = 0 then
|
||||
begin
|
||||
|
||||
// Узнаём нужную длинную строки
|
||||
var l: integer;
|
||||
gl.GetShaderiv(Result, ShaderParameterName.INFO_LOG_LENGTH, l);
|
||||
|
||||
// Выделяем достаточно памяти чтоб сохранить строку
|
||||
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
|
||||
|
||||
// Получаем строку логов
|
||||
gl.GetShaderInfoLog(Result, l, IntPtr.Zero, ptr);
|
||||
|
||||
// Преобразовываем в управляемую строку
|
||||
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
|
||||
|
||||
// И в конце обязательно освобождаем, чтобы не было утечек памяти
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
|
||||
gl.DeleteShader(Result);
|
||||
|
||||
raise new System.ArgumentException($'{fname}:{#10}{log}');
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Shader}
|
||||
|
||||
{$region Program}
|
||||
|
||||
function InitProgram(params shaders: array of gl_shader): gl_program;
|
||||
begin
|
||||
Result := gl.CreateProgram;
|
||||
|
||||
foreach var sh in shaders do
|
||||
gl.AttachShader(Result, sh);
|
||||
|
||||
gl.LinkProgram(Result);
|
||||
// Всё то же самое что и у шейдеров
|
||||
var link_ok: integer;
|
||||
gl.GetProgramiv(Result, ProgramPropertyARB.LINK_STATUS, link_ok);
|
||||
if link_ok = 0 then
|
||||
begin
|
||||
|
||||
var l: integer;
|
||||
gl.GetProgramiv(Result, ProgramPropertyARB.INFO_LOG_LENGTH, l);
|
||||
var ptr := System.Runtime.InteropServices.Marshal.AllocHGlobal(l);
|
||||
|
||||
gl.GetProgramInfoLog(Result, l, IntPtr.Zero, ptr);
|
||||
var log := System.Runtime.InteropServices.Marshal.PtrToStringAnsi(ptr);
|
||||
|
||||
System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
|
||||
gl.DeleteProgram(Result);
|
||||
|
||||
raise new System.ArgumentException(log);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
{$endregion Program}
|
||||
|
||||
end.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#version 460 core
|
||||
|
||||
// Ничего не делаем
|
||||
// Какую точку передали - такую и вернёт
|
||||
void main() { }
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#version 460 core
|
||||
|
||||
// Подсматривал сюда:
|
||||
// https://stackoverflow.com/questions/44354589/optimizing-mandelbrot-fractal
|
||||
|
||||
noperspective in vec2 logic_pos;
|
||||
|
||||
layout(location = 0) uniform int point_count;
|
||||
layout(binding = 0) buffer points_in {
|
||||
dvec2 data[];
|
||||
} points;
|
||||
|
||||
layout(location = 1) uniform double camera_aspect_ratio;
|
||||
layout(location = 2) uniform double camera_scale;
|
||||
layout(location = 3) uniform dvec2 camera_pos;
|
||||
|
||||
/**
|
||||
layout(binding = 1) buffer temp_otp {
|
||||
dvec2 data[3];
|
||||
} temp;
|
||||
/**/
|
||||
|
||||
out vec3 color;
|
||||
|
||||
// Радиус белой точки
|
||||
// (хотел сделать взаимодействие с основным рисунком, но и так лагает)
|
||||
const float point_r = 5;
|
||||
// Скорость затемнения при отдалении от точки
|
||||
const float fall_speed = 0.6;
|
||||
|
||||
const float extra_scale = 0.01;
|
||||
|
||||
const int MAX_DEPTH = 128;
|
||||
int CalculateMandelbrotDepth(dvec2 c, dvec2 start) {
|
||||
c *= extra_scale;
|
||||
//start *= extra_scale;
|
||||
if (dot(start,start)>2) return 0;
|
||||
|
||||
dvec2 prev[MAX_DEPTH];
|
||||
dvec2 curr = start;
|
||||
|
||||
for (int depth = 1; depth < MAX_DEPTH; ++depth) {
|
||||
prev[depth-1] = curr;
|
||||
|
||||
curr = dvec2( curr.x*curr.x - curr.y*curr.y, 2*curr.x*curr.y )+c;
|
||||
|
||||
if (dot(curr,curr)>2) return depth;
|
||||
for (int i = depth-1; i>=0; --i)
|
||||
if (curr == prev[i]) return depth;
|
||||
}
|
||||
|
||||
return MAX_DEPTH;
|
||||
}
|
||||
|
||||
// All components are in the range [0…1], including hue.
|
||||
vec3 hsv2rgb(float hue)
|
||||
{
|
||||
vec3 c = {hue, 0.75, 0.5};
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
// Считаем в фрагментном шейдере, потому что интерполяция
|
||||
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
|
||||
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
|
||||
pos *= camera_scale;
|
||||
pos += camera_pos;
|
||||
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
dvec2 dp = pos-points.data[i];
|
||||
if (dot(dp,dp)<point_r*point_r) {
|
||||
color = vec3(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int depth = CalculateMandelbrotDepth(pos, dvec2(0));
|
||||
/**
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
//TODO Какое-то взаимодействие
|
||||
}
|
||||
/**/
|
||||
|
||||
if (depth == 0)
|
||||
color = vec3(0); else
|
||||
{
|
||||
//color = vec3( float(depth) / MAX_DEPTH ); // Чёрно-белый рисунок
|
||||
//color = hsv2rgb( float(depth) / MAX_DEPTH * 0.8 + 0.1 ); // Все цвета радуги
|
||||
color = hsv2rgb( fract(float(depth) / MAX_DEPTH*radians(180)) ); // Псевдо-случайные цвета
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#version 460 core
|
||||
|
||||
layout(points) in;
|
||||
layout(triangle_strip, max_vertices = 4) out;
|
||||
out vec2 logic_pos;
|
||||
|
||||
void SendVertex(float coord1, float coord2) {
|
||||
logic_pos = vec2(coord1, coord2);
|
||||
gl_Position = vec4(coord1, coord2, 0, 1);
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
// На вход приходит одна точка без данных
|
||||
// На выход посылаем 2 треугольника, покрывающих весь экран
|
||||
// При чём передаём логическую позицию на экране в виде logic_pos
|
||||
// В фрагментном шейдере значения logic_pos интерполируются с noperspective
|
||||
void main() {
|
||||
SendVertex(-1,-1);
|
||||
SendVertex(-1,+1);
|
||||
SendVertex(+1,-1);
|
||||
SendVertex(+1,+1);
|
||||
EndPrimitive();
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#version 460 core
|
||||
|
||||
noperspective in vec2 logic_pos;
|
||||
|
||||
layout(location = 0) uniform int point_count;
|
||||
layout(binding = 0) buffer points_in {
|
||||
dvec2 data[];
|
||||
} points;
|
||||
|
||||
layout(location = 1) uniform double camera_aspect_ratio;
|
||||
layout(location = 2) uniform double camera_scale;
|
||||
layout(location = 3) uniform dvec2 camera_pos;
|
||||
|
||||
/**
|
||||
layout(binding = 1) buffer temp_otp {
|
||||
dvec2 data[3];
|
||||
} temp;
|
||||
/**/
|
||||
|
||||
out vec3 color;
|
||||
|
||||
// Радиус красной точки
|
||||
const float point_r = 5;
|
||||
// Скорость затемнения при отдалении от точки
|
||||
const float fall_speed = 0.6;
|
||||
|
||||
void main() {
|
||||
|
||||
// Считаем в фрагментном шейдере, потому что интерполяция
|
||||
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
|
||||
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
|
||||
pos *= camera_scale;
|
||||
pos += camera_pos;
|
||||
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
dvec2 dp = pos-points.data[i];
|
||||
if (dot(dp,dp)<point_r*point_r) {
|
||||
color = vec3(0.7, 0, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double res = 0;
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
double curr = distance(pos, points.data[i]) / 10;
|
||||
curr = fract(curr);
|
||||
res += curr;
|
||||
}
|
||||
res /= point_count;
|
||||
//res = fract(res);
|
||||
|
||||
color = vec3( res );
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#version 460 core
|
||||
|
||||
noperspective in vec2 logic_pos;
|
||||
|
||||
layout(location = 0) uniform int point_count;
|
||||
layout(binding = 0) buffer points_in {
|
||||
dvec2 data[];
|
||||
} points;
|
||||
|
||||
layout(location = 1) uniform double camera_aspect_ratio;
|
||||
layout(location = 2) uniform double camera_scale;
|
||||
layout(location = 3) uniform dvec2 camera_pos;
|
||||
|
||||
layout(binding = 1) buffer temp_otp {
|
||||
dvec2 data[3];
|
||||
} temp;
|
||||
|
||||
out vec3 color;
|
||||
|
||||
// Радиус красной точки
|
||||
const float point_r = 5;
|
||||
const float point_r_sqr = point_r*point_r;
|
||||
// Скорость затемнения при отдалении от точки
|
||||
const float fall_speed = 0.3;
|
||||
|
||||
void main() {
|
||||
|
||||
// Считаем в фрагментном шейдере, потому что интерполяция
|
||||
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
|
||||
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
|
||||
pos *= camera_scale;
|
||||
pos += camera_pos;
|
||||
|
||||
// Храним и сравниваем квадраты расстояний,
|
||||
// чтобы не считать корень лишний раз - он дорогой
|
||||
double min_r_sqr = double(1)/0;
|
||||
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
dvec2 dp = pos - points.data[i];
|
||||
double r_sqr = dot(dp, dp);
|
||||
if (r_sqr<point_r_sqr) {
|
||||
color = vec3(0.7, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (r_sqr<min_r_sqr) min_r_sqr = r_sqr;
|
||||
}
|
||||
|
||||
// Функция формы y=1/x, но маштабированная чтобы y(point_r)=1
|
||||
double fall_sqr = point_r_sqr/min_r_sqr;
|
||||
// И в данном случае результат в квадрате, поэтому дальше степень делим на 2
|
||||
float fall = pow(float(fall_sqr), fall_speed/2);
|
||||
|
||||
// Ступеньки из оттенков серого
|
||||
if (false) {
|
||||
int color_count = 64;
|
||||
fall = round(fall*color_count) / color_count;
|
||||
}
|
||||
|
||||
color = vec3( fall );
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
#version 460 core
|
||||
|
||||
noperspective in vec2 logic_pos;
|
||||
|
||||
layout(location = 0) uniform int point_count;
|
||||
layout(binding = 0) buffer points_in {
|
||||
dvec2 data[];
|
||||
} points;
|
||||
|
||||
layout(location = 1) uniform double camera_aspect_ratio;
|
||||
layout(location = 2) uniform double camera_scale;
|
||||
layout(location = 3) uniform dvec2 camera_pos;
|
||||
|
||||
/**
|
||||
layout(binding = 1) buffer temp_otp {
|
||||
double data[3];
|
||||
} temp;
|
||||
/**/
|
||||
|
||||
out vec3 color;
|
||||
|
||||
const int arm_count = 5;
|
||||
|
||||
void main() {
|
||||
|
||||
// Считаем в фрагментном шейдере, потому что интерполяция
|
||||
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
|
||||
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
|
||||
pos *= camera_scale;
|
||||
pos += camera_pos;
|
||||
|
||||
double total_ang = 0;
|
||||
double total_r = 0;
|
||||
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
dvec2 dp = pos - points.data[i];
|
||||
dp /= point_count;
|
||||
|
||||
// Угол от 0 до 1
|
||||
float angle = atan(
|
||||
float(dp.y/abs(dp.x)), float(sign(dp.x))
|
||||
)/radians(360)+0.5;
|
||||
total_ang += double(angle)*arm_count;
|
||||
|
||||
double radius = sqrt(dot(dp,dp));
|
||||
radius = log2(float(radius));
|
||||
total_r += radius;
|
||||
|
||||
}
|
||||
|
||||
//total_r = log2(float(total_r));
|
||||
double res = total_ang + total_r;
|
||||
|
||||
/**
|
||||
if (distance(pos, dvec2(temp.data[0],temp.data[1])) < camera_scale/500)
|
||||
temp.data[2] = res;
|
||||
/**/
|
||||
|
||||
//res /= point_count;
|
||||
res = fract(res);
|
||||
|
||||
color = vec3( res );
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
#version 460 core
|
||||
|
||||
noperspective in vec2 logic_pos;
|
||||
|
||||
layout(location = 0) uniform int point_count;
|
||||
layout(binding = 0) buffer points_in {
|
||||
dvec2 data[];
|
||||
} points;
|
||||
|
||||
layout(location = 1) uniform double camera_aspect_ratio;
|
||||
layout(location = 2) uniform double camera_scale;
|
||||
layout(location = 3) uniform dvec2 camera_pos;
|
||||
|
||||
/**
|
||||
layout(binding = 1) buffer temp_otp {
|
||||
dvec2 data[3];
|
||||
} temp;
|
||||
/**/
|
||||
|
||||
out vec3 color;
|
||||
|
||||
// Радиус красной точки
|
||||
const float point_r = 5;
|
||||
// Скорость затемнения при отдалении от точки
|
||||
const float fall_speed = 0.6;
|
||||
|
||||
const bool fall_range_debug = false;
|
||||
|
||||
void main() {
|
||||
|
||||
// В идеале это надо считать в геометрическом шейдере,
|
||||
// или даже на CPU... Но для этого надо менять код, общий с другими примерами
|
||||
float min_r_sum = 0;
|
||||
dvec2 d_center = {0,0};
|
||||
for (int i=0; i<point_count; ++i) d_center += points.data[i];
|
||||
d_center /= point_count;
|
||||
vec2 center = vec2( d_center );
|
||||
|
||||
for (int i=0; i<point_count; ++i)
|
||||
min_r_sum += distance(center, vec2(points.data[i]));
|
||||
|
||||
// Считаем в фрагментном шейдере, потому что интерполяция
|
||||
// 64-битного logic_pos (с типом dvec2) в OpenGL запрещена
|
||||
dvec2 pos = { logic_pos.x*camera_aspect_ratio, logic_pos.y };
|
||||
pos *= camera_scale;
|
||||
pos += camera_pos;
|
||||
|
||||
if (fall_range_debug && distance(pos, d_center)<point_r) {
|
||||
color = vec3(0, 0, 0.7);
|
||||
return;
|
||||
}
|
||||
|
||||
float r_sum = 0;
|
||||
for (int i=0; i<point_count; ++i) {
|
||||
dvec2 dp = pos - points.data[i];
|
||||
float r = sqrt(float(dot(dp, dp)));
|
||||
if (r<point_r) {
|
||||
color = vec3(0.7, 0, 0);
|
||||
return;
|
||||
}
|
||||
r_sum += r;
|
||||
}
|
||||
|
||||
// Функция формы y=1/x, но маштабированная чтобы y(min_r_sum)=1
|
||||
double d_fall = min_r_sum/r_sum;
|
||||
float fall = pow(float(d_fall), fall_speed);
|
||||
|
||||
// Ступеньки из оттенков серого
|
||||
if (true) {
|
||||
int color_count = 64;
|
||||
fall = round(fall*color_count) / color_count;
|
||||
}
|
||||
|
||||
// center всё же не всегда имеет наименьшую сумму расстояний
|
||||
// Поэтому min_r_sum может считаться неправильно и fall оказываться >1
|
||||
// Наверное, нужен итерационный алгоритм на стороне CPU, чтобы адекватно это сделать
|
||||
// Напишите если есть идеи лучше
|
||||
if (fall_range_debug && fall>1) {
|
||||
color = vec3(0.8, 0, 0.8);
|
||||
return;
|
||||
}
|
||||
|
||||
color = vec3( fall );
|
||||
}
|
||||
|
||||
|
||||
285
InstallerSamples/OpenGL и OpenCL/OpenGL/Точки на поле/Точки.pas
Normal file
285
InstallerSamples/OpenGL и OpenCL/OpenGL/Точки на поле/Точки.pas
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
{$reference System.Windows.Forms.dll}
|
||||
{$reference System.Drawing.dll}
|
||||
|
||||
// Примеры фрагментных шейдеров
|
||||
//
|
||||
// ========== Управление ==========
|
||||
// ЛКМ: Поставить точку
|
||||
// СКМ: Удалить ближайшую точку
|
||||
// ПКМ: Схватить и тащить ближайшую точку
|
||||
// Колёсико мыши: Маштаб
|
||||
// Пробел: Вернуть камеру
|
||||
// Цифры от 1 до 5: Переключение шейдеров
|
||||
|
||||
uses System.Windows.Forms;
|
||||
uses System;
|
||||
uses OpenGL;
|
||||
|
||||
uses Common in '..\Common';
|
||||
|
||||
{$apptype windows}
|
||||
|
||||
type
|
||||
CameraData = record
|
||||
aspect := real.NaN;
|
||||
scale := 500.0;
|
||||
pos := default(Vec2d);
|
||||
end;
|
||||
|
||||
begin
|
||||
var f := new Form;
|
||||
f.WindowState := FormWindowState.Maximized;
|
||||
// f.ControlBox := false;
|
||||
|
||||
// Моментальное закрытие при Alt+F4 и Esc
|
||||
f.Closed += (o,e)->Halt();
|
||||
f.KeyUp += (o,e)->
|
||||
case e.KeyCode of
|
||||
Keys.Escape: Halt;
|
||||
end;
|
||||
|
||||
var camera := new CameraData;
|
||||
|
||||
var need_resize := false;
|
||||
var ev_resized_render_complete := new System.Threading.AutoResetEvent(false);
|
||||
f.Shown += (o,e)->
|
||||
begin
|
||||
need_resize := true;
|
||||
f.Resize += (o,e)->
|
||||
begin
|
||||
need_resize := true;
|
||||
// Чтобы не мигало - ждём завершения одной перерисовки
|
||||
// в потоке формы, то есть блокируя отсыл информации системе
|
||||
ev_resized_render_complete.WaitOne;
|
||||
end;
|
||||
end;
|
||||
|
||||
var curr_fragment_shader := 0;
|
||||
var point_count := 0;
|
||||
var points := new Vec2d[4];
|
||||
var mouse_pos := new Vec2d(real.NaN, real.NaN);
|
||||
{$region Управление}
|
||||
var points_updated := 0;
|
||||
begin
|
||||
|
||||
var CoordsFromScreen := function(X,Y: integer): Vec2d ->
|
||||
begin
|
||||
var logic_pos := new Vec2d(X/f.ClientSize.Width-0.5, 0.5-Y/f.ClientSize.Height)*2;
|
||||
var pos := new Vec2d(logic_pos.val0*camera.aspect, logic_pos.val1);
|
||||
Result := pos*camera.scale + camera.pos;
|
||||
end;
|
||||
|
||||
var ClosestPointInd := function(pos: Vec2d): integer ->
|
||||
begin
|
||||
Assert(point_count<>0);
|
||||
var min_r_sqr := (pos-points[0]).SqrLength;
|
||||
Result := 0;
|
||||
for var i := 1 to point_count-1 do
|
||||
begin
|
||||
var r_sqr := (pos-points[i]).SqrLength;
|
||||
if r_sqr < min_r_sqr then
|
||||
begin
|
||||
min_r_sqr := r_sqr;
|
||||
Result := i;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
f.MouseWheel += (o,e)->
|
||||
begin
|
||||
var pos := CoordsFromScreen(e.X, e.Y);
|
||||
|
||||
var pow := 1 - Sign(e.Delta)*0.1;
|
||||
camera.scale := camera.scale * pow;
|
||||
|
||||
camera.pos := camera.pos + (pos-CoordsFromScreen(e.X, e.Y));
|
||||
end;
|
||||
|
||||
f.KeyDown += (o,e)->
|
||||
case e.KeyCode of
|
||||
Keys.Space:
|
||||
begin
|
||||
var n_camera := new CameraData;
|
||||
camera.scale := n_camera.scale;
|
||||
camera.pos := n_camera.pos;
|
||||
end;
|
||||
Keys.D1..Keys.D5: curr_fragment_shader := integer(e.KeyCode)-integer(Keys.D1);
|
||||
end;
|
||||
|
||||
var grabbed_point_ind := default(integer?);
|
||||
f.MouseDown += (o,e)->
|
||||
case e.Button of
|
||||
MouseButtons.Left:
|
||||
begin
|
||||
if point_count=points.Length then
|
||||
// По хорошему буфер OpenGL тоже надо ресайзить
|
||||
// Но сейчас он полностью пересоздаётся
|
||||
SetLength(points, points.Length*2);
|
||||
points[point_count] := CoordsFromScreen(e.X,e.Y);
|
||||
point_count += 1;
|
||||
System.Threading.Interlocked.Exchange(points_updated, 1);
|
||||
end;
|
||||
MouseButtons.Middle:
|
||||
if point_count<>0 then
|
||||
begin
|
||||
var pos := CoordsFromScreen(e.X,e.Y);
|
||||
var ind := ClosestPointInd(pos);
|
||||
point_count -= 1;
|
||||
if ind<>point_count then points[ind] := points[point_count];
|
||||
System.Threading.Interlocked.Exchange(points_updated, 1);
|
||||
end;
|
||||
MouseButtons.Right:
|
||||
if point_count<>0 then
|
||||
begin
|
||||
var pos := CoordsFromScreen(e.X,e.Y);
|
||||
var ind := ClosestPointInd(pos);
|
||||
grabbed_point_ind := ind;
|
||||
points[ind] := pos;
|
||||
System.Threading.Interlocked.Exchange(points_updated, 1);
|
||||
end;
|
||||
end;
|
||||
|
||||
f.MouseMove += (o,e)->
|
||||
begin
|
||||
var pos := CoordsFromScreen(e.X,e.Y);
|
||||
mouse_pos := pos;
|
||||
if grabbed_point_ind<>nil then
|
||||
begin
|
||||
points[grabbed_point_ind.Value] := pos;
|
||||
System.Threading.Interlocked.Exchange(points_updated, 1);
|
||||
end;
|
||||
end;
|
||||
|
||||
f.MouseUp += (o,e)->
|
||||
case e.Button of
|
||||
MouseButtons.Right:
|
||||
if grabbed_point_ind<>nil then
|
||||
begin
|
||||
points[grabbed_point_ind.Value] := CoordsFromScreen(e.X,e.Y);
|
||||
grabbed_point_ind := nil;
|
||||
System.Threading.Interlocked.Exchange(points_updated, 1);
|
||||
end;
|
||||
end;
|
||||
|
||||
end;
|
||||
{$endregion Управление}
|
||||
|
||||
gl_gdi.SetupControlRedrawing(f, gl_gdi.InitControl(f), EndFrame->
|
||||
try
|
||||
gl := new OpenGL.gl<PlWin>;
|
||||
|
||||
var sprog: gl_program;
|
||||
var last_fragment_shader := -1;
|
||||
|
||||
var uniform_point_count := 0;
|
||||
var buffer_points: gl_buffer;
|
||||
gl.CreateBuffers(1, buffer_points);
|
||||
gl.BindBufferBase(BufferTarget.SHADER_STORAGE_BUFFER, 0, buffer_points);
|
||||
|
||||
var uniform_aspect := 1;
|
||||
var uniform_scale := 2;
|
||||
var uniform_pos := 3;
|
||||
|
||||
// Для дебага
|
||||
// var buffer_temp: gl_buffer;
|
||||
// gl.CreateBuffers(1, buffer_temp);
|
||||
// gl.NamedBufferData(buffer_temp, new IntPtr(3*sizeof(real)), IntPtr.Zero, VertexBufferObjectUsage.DYNAMIC_READ);
|
||||
// gl.BindBufferBase(BufferTarget.SHADER_STORAGE_BUFFER, 1, buffer_temp);
|
||||
|
||||
var timings_max_count := 30;
|
||||
var timings := new Queue<TimeSpan>(timings_max_count);
|
||||
var total_time := TimeSpan.Zero;
|
||||
var sw := new Stopwatch;
|
||||
|
||||
while true do
|
||||
begin
|
||||
var need_set_resize_ev := false;
|
||||
if need_resize then
|
||||
begin
|
||||
var w_size := f.ClientSize;
|
||||
|
||||
gl.Viewport(0,0, w_size.Width,w_size.Height);
|
||||
need_set_resize_ev := true;
|
||||
|
||||
camera.aspect := w_size.Width/w_size.Height;
|
||||
end;
|
||||
|
||||
if last_fragment_shader<>curr_fragment_shader then
|
||||
begin
|
||||
last_fragment_shader := curr_fragment_shader;
|
||||
|
||||
gl.DeleteProgram(sprog);
|
||||
sprog := InitProgram(
|
||||
InitShader('Empty.vert', ShaderType.VERTEX_SHADER),
|
||||
InitShader('SinglePointToScreen.geom', ShaderType.GEOMETRY_SHADER),
|
||||
InitShader(|
|
||||
'Минимум расстояний.frag',
|
||||
'Сумма расстояний.frag',
|
||||
'Волны.frag',
|
||||
'Спирали.frag',
|
||||
'Mandelbrot.frag'
|
||||
|[last_fragment_shader], ShaderType.FRAGMENT_SHADER)
|
||||
);
|
||||
// Применяем вне цикла, потому что это единственная шейдерная программа
|
||||
gl.UseProgram(sprog);
|
||||
points_updated := 1;
|
||||
end;
|
||||
|
||||
if System.Threading.Interlocked.Exchange(points_updated, 0)<>0 then
|
||||
begin
|
||||
gl.Uniform1i(uniform_point_count, point_count);
|
||||
// Плохо - пересоздаём всё тело буфера при каждой перерисовке
|
||||
// Но по сравнению с фрагментным шейдером - это капля в море
|
||||
gl.NamedBufferData(buffer_points, new IntPtr(point_count*sizeof(vec2d)), points, VertexBufferObjectUsage.DYNAMIC_READ);
|
||||
// Записать point_count точек из массива можно так:
|
||||
// gl.NamedBufferSubData(buffer_points, IntPtr.Zero, new IntPtr(point_count * sizeof(Vec2d)), points);
|
||||
end;
|
||||
|
||||
sw.Restart;
|
||||
|
||||
// Копируем все данные сразу в локальную переменную
|
||||
var camera := camera;
|
||||
// И затем в юниформы
|
||||
gl.Uniform1d(uniform_aspect, camera.aspect);
|
||||
gl.Uniform1d(uniform_scale, camera.scale);
|
||||
gl.Uniform2dv(uniform_pos, 1, camera.pos);
|
||||
|
||||
// gl.NamedBufferSubData(buffer_temp, new IntPtr(0*sizeof(real)), new IntPtr(2*sizeof(real)), mouse_pos);
|
||||
|
||||
// Вызываем шейдерную программу один раз, не передавая данные
|
||||
// Геометричейский шейдер сделает из этого
|
||||
gl.DrawArrays(PrimitiveType.POINTS, 0,1);
|
||||
|
||||
// var temp_data := new real[1];
|
||||
// gl.GetNamedBufferSubData(buffer_temp, new IntPtr(2*sizeof(real)), new IntPtr(1*sizeof(real)), temp_data);
|
||||
|
||||
var err := gl.GetError;
|
||||
if err<>ErrorCode.NO_ERROR then MessageBox.Show(err.ToString);
|
||||
|
||||
gl.Finish;
|
||||
if need_set_resize_ev then
|
||||
ev_resized_render_complete.Set;
|
||||
|
||||
var curr_time := sw.Elapsed;
|
||||
if timings.Count=timings_max_count then
|
||||
total_time := total_time-timings.Dequeue;
|
||||
total_time := total_time+curr_time;
|
||||
timings += curr_time;
|
||||
|
||||
var fps := timings.Count/total_time.TotalSeconds;
|
||||
var form_text := $'{fps:N2} fps, {1000/fps:N2} mspf, {point_count} points, scale={camera.scale}, pos={camera.pos}';
|
||||
// form_text := $'temp_data={_ObjectToString(temp_data)}, '+form_text;
|
||||
f.Text := form_text;
|
||||
EndFrame;
|
||||
end;
|
||||
|
||||
except
|
||||
on e: Exception do
|
||||
begin
|
||||
MessageBox.Show(e.ToString);
|
||||
Halt;
|
||||
end;
|
||||
end);
|
||||
|
||||
System.Windows.Forms.Application.Run(f);
|
||||
end.
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
|
||||
//*****************************************************************************************************\\
|
||||
// Copyright (©) Cergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
|
||||
// Copyright (©) Sergey Latchenko ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
|
||||
// This code is distributed under the Unlicense
|
||||
// For details see LICENSE file or this:
|
||||
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
|
||||
//*****************************************************************************************************\\
|
||||
// Copyright (©) Сергей Латченко ( github.com/SunSerega | forum.mmcs.sfedu.ru/u/sun_serega )
|
||||
// Этот код распространяется с лицензией Unlicense
|
||||
// Данный код распространяется с лицензией Unlicense
|
||||
// Подробнее в файле LICENSE или тут:
|
||||
// https://github.com/SunSerega/POCGL/blob/master/LICENSE
|
||||
//*****************************************************************************************************\\
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue