Проблемы с быстрым мерцанием OpenGL - и теперь изображение не отображается

У меня проблемы с OpenGL. Прежде всего, у меня есть собственный элемент управления, который я создаю, инкапсулируя OpenGL. У меня проблема, когда он быстро мерцает, как будто он «мигает». Элемент управления был слишком большим и сложным, чтобы публиковать его здесь, поэтому я сделал новое небольшое демонстрационное приложение, чтобы продемонстрировать и воссоздать тот же сценарий.

Проблема сейчас: я не получаю никакого изображения. В моем пользовательском элементе управления все будет хорошо отображаться, но, поскольку я скопировал код и убрал его в небольшом демонстрационном приложении, он не будет отображать изображение. Итак, здесь у меня есть две проблемы: мерцание (или мигание), и теперь изображение даже не отображается. Мерцания не было в прошлом, но после серьезной ревизии кода оно начало мерцать. Было слишком много изменений в коде, чтобы объяснить, что именно было изменено, почти все.

Фон виден, поэтому я знаю, что он что-то рисует (он цветной). Он должен рисовать только один куб для демонстрационных целей, но я ничего не вижу. Мне пришлось сократить это примерно с 1000 строк кода до 300.

Это не то, что вы обычно называете мерцанием, на самом деле это скорее мигание или мигание, представьте, что поворотник автомобиля мигает и выключается. Это определенно связано с таймером, потому что чем выше интервал, который я устанавливаю на таймере, тем медленнее он мигает.

Почему я ничего не вижу? И когда это исправлено, почему он так сильно мерцает?

Вот код одной формы, DFM не требуется:

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, Winapi.OpenGL,
  System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    FDrawing: Bool;
    FDC: HDC;
    FRC: HGLRC;
    FDL: glUint;
    FTimer: TTimer;
    procedure Draw;
    procedure SetDC(const Value: HDC);
    procedure SetRC(const Value: HGLRC);
    procedure SetDL(const Value: glUint);
  public
    property DC: HDC read FDC write SetDC;
    property RC: HGLRC read FRC write SetRC;
    property DL: glUint read FDL write SetDL;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  PixelFormat: glUint;
  pfd: TPIXELFORMATDESCRIPTOR;
begin
  FDrawing := False;
  FDC := GetDC(Handle);
  with pfd do begin
    nSize := SizeOf(TPIXELFORMATDESCRIPTOR);
    nVersion := 1; // The version of this data structure
    dwFlags := PFD_DRAW_TO_WINDOW // Buffer supports drawing to window
      or PFD_SUPPORT_OPENGL // Buffer supports OpenGL drawing
      or PFD_DOUBLEBUFFER; // Supports double buffering
    iPixelType := PFD_TYPE_RGBA; // RGBA color format
    cColorBits := 32; // OpenGL color depth
    cRedBits := 0; // Number of red bitplanes
    cRedShift := 0; // Shift count for red bitplanes
    cGreenBits := 0; // Number of green bitplanes
    cGreenShift := 0; // Shift count for green bitplanes
    cBlueBits := 0; // Number of blue bitplanes
    cBlueShift := 0; // Shift count for blue bitplanes
    cAlphaBits := 0; // Not supported
    cAlphaShift := 0; // Not supported
    cAccumBits := 0; // No accumulation buffer
    cAccumRedBits := 0; // Number of red bits in a-buffer
    cAccumGreenBits := 0; // Number of green bits in a-buffer
    cAccumBlueBits := 0; // Number of blue bits in a-buffer
    cAccumAlphaBits := 0; // Number of alpha bits in a-buffer
    cDepthBits := 16; // Specifies the depth of the depth buffer
    cStencilBits := 0; // Turn off stencil buffer
    cAuxBuffers := 0; // Not supported
    iLayerType := PFD_MAIN_PLANE; // Ignored
    bReserved := 0; // Number of overlay and underlay planes
    dwLayerMask := 0; // Ignored
    dwVisibleMask := 0; // Transparent color of underlay plane
    dwDamageMask := 0; // Ignored
  end;
  PixelFormat := ChoosePixelFormat(FDC, @pfd);
  SetPixelFormat(FDC, PixelFormat, @pfd);
  FRC := wglCreateContext(FDC);
  wglMakeCurrent(FDC, FRC);
  FormResize(nil);
  wglMakeCurrent(FDC, FRC);
  glClearColor(0.8, 0.8, 0.9, 0.0);
  glShadeModel(GL_FLAT);
  glClearDepth(1.0);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_GREATER, 0.4);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
  glEnable(GL_TEXTURE_2D);
  glNewList(FDL, GL_COMPILE);
    glBegin(GL_QUADS);
      // Front Face
      glTexCoord2f(0.0, 0.0);
      glVertex3f(-2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(-2.0, 1.0, 1.0);
      // Back Face
      glTexCoord2f(2.0, 0.0);
      glVertex3f(-2.0, -1.0, -1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(-2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, -1.0);
      // Top Face
      glTexCoord2f(0.0, 1.0);
      glVertex3f(-2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(-2.0, 1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
      // Bottom Face
      glTexCoord2f(2.0, 1.0);
      glVertex3f(-2.0, -1.0, -1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, -1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(-2.0, -1.0, 1.0);
      // Left Face
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, -1.0);
      glTexCoord2f(1.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(1.0, 1.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
    glEnd();
  glEndList();
  FTimer:= TTimer.Create(nil);
  FTimer.OnTimer:= Timer1Timer;
  FTimer.Interval:= 100;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FTimer.Free;
  if (not wglMakeCurrent(FDC, 0)) then
    MessageBox(0, 'Release of DC and RC failed!', 'Error', MB_OK or MB_ICONERROR);
  if (not wglDeleteContext(FRC)) then begin
    MessageBox(0, 'Release of rendering context failed!', 'Error', MB_OK or MB_ICONERROR);
    FRC := 0;
  end;
  if ((FDC > 0) and (ReleaseDC(Handle, FDC) = 0)) then begin
    MessageBox(0, 'Release of device context failed!', 'Error', MB_OK or MB_ICONERROR);
    FDC := 0;
  end;
end;

procedure TForm1.Draw;
var
  I: Integer;
begin
  if not FDrawing then begin
    FDrawing := TRUE;
    try
      glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      glEnable(GL_NORMALIZE);
      glShadeModel(GL_FLAT);
      glCullFace(GL_BACK);
      glLoadIdentity;
      glPushMatrix();
      glCallList(DL);
      glPopMatrix();
      SwapBuffers(wglGetCurrentDC);
    finally
      FDrawing := False;
    end;
  end;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
  glViewport(0, 0, Width, Height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, Width / Height, 0.1, 500.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
end;

procedure TForm1.SetDC(const Value: HDC);
begin
  FDC := Value;
end;

procedure TForm1.SetDL(const Value: glUint);
begin
  FDL := Value;
end;

procedure TForm1.SetRC(const Value: HGLRC);
begin
  FRC := Value;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Draw;
end;

end.

Приведенный выше код довольно сильно отличается от того, как работает мой исходный код. Исходная процедура Draw выполняет итерацию по списку объектов, каждый объект содержит собственную процедуру Draw. Таким образом, процедура отрисовки элемента управления подготавливает общую сцену, а затем отрисовывает каждый «элемент» один за другим, например:

procedure TGLImage.Draw;
var
  X: Integer;
begin
  if not FDrawing then begin
    FDrawing := TRUE;
    try
      glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
      glEnable(GL_NORMALIZE);
      glShadeModel(GL_FLAT);
      glCullFace(GL_BACK);
      glLoadIdentity();

      glRotatef(FElapsedTime / 70, 0, 0, 1);
      glRotatef(90, 0, 1, 0);
      glTranslatef(-FElapsedTime / 400, 0, 0);

      if FInitialized then begin
        for X := 0 to FItems.Count - 1 do begin
          FItems[X].Draw;
        end;
      end;

      SwapBuffers(wglGetCurrentDC);
    finally
      FDrawing := False;
    end;
  end;
end;

И вот один из предметов, который он рисует...

constructor TGLBeam.Create(AOwner: TGLItems);
begin
  inherited;
  glNewList(DL, GL_COMPILE);
    glBegin(GL_QUADS);
      // Front Face
      glTexCoord2f(0.0, 0.0);
      glVertex3f(-2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(-2.0, 1.0, 1.0);
      // Back Face
      glTexCoord2f(2.0, 0.0);
      glVertex3f(-2.0, -1.0, -1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(-2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, -1.0);
      // Top Face
      glTexCoord2f(0.0, 1.0);
      glVertex3f(-2.0, 1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(-2.0, 1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(2.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
      // Bottom Face
      glTexCoord2f(2.0, 1.0);
      glVertex3f(-2.0, -1.0, -1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, -1.0, -1.0);
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(2.0, 0.0);
      glVertex3f(-2.0, -1.0, 1.0);
      // Left Face
      glTexCoord2f(0.0, 0.0);
      glVertex3f(2.0, -1.0, -1.0);
      glTexCoord2f(1.0, 0.0);
      glVertex3f(2.0, -1.0, 1.0);
      glTexCoord2f(1.0, 1.0);
      glVertex3f(2.0, 1.0, 1.0);
      glTexCoord2f(0.0, 1.0);
      glVertex3f(2.0, 1.0, -1.0);
    glEnd();
  glEndList();
end;

procedure TGLBeam.Draw;
var
  I: Integer;
begin
  glRotatef(Directions.X, 1.0, 0.0, 0.0);
  glRotatef(Directions.Y, 0.0, 1.0, 0.0);
  glRotatef(Directions.Z, 0.0, 0.0, 1.0);
  for I := 1 to 10 do begin
    //Main Center
    glPushMatrix();
    glTranslatef(I * 4 + Owner.Owner.ClockTime * 4, 0, 0);
    glCallList(DL);
    glPopMatrix();
    //Above
    glPushMatrix();
    glTranslatef(I * 4 + Owner.Owner.ClockTime * 4, 6, 0);
    glCallList(DL);
    glPopMatrix();
  end;
end;

person Jerry Dodge    schedule 24.05.2012    source источник
comment
Как правило, в программировании для Windows вы никогда не должны делать никаких выводов в DC (контекст устройства), кроме как в ответ на сообщение WM_PAINT (событие OnPaint в Delphi). Обе проблемы, вероятно, вызваны тем, что ваш код борется с отрисовкой Windows по умолчанию. Вероятно, вы можете исправить это, переместив весь ваш код рисования в обработчик событий OnPaint (или просто переместив вызов Draw в это событие).   -  person Ken White    schedule 24.05.2012
comment
@KenWhite Только что попробовал, но от этого стало еще хуже. Раньше он работал нормально, используя метод таймера, плавно и без мерцания. Сейчас как то как только заканчивает рисовать сразу очищает что нарисовал.   -  person Jerry Dodge    schedule 24.05.2012
comment
VCL использует двойную буферизацию (отрисовка растрового изображения за пределами экрана, а затем обновление всей поверхности сразу путем копирования этого растрового изображения), чтобы избежать мерцания. Сам я не специалист по OpenGL, но вы могли бы сделать то же самое. Я уверен, что у некоторых программ с открытым исходным кодом есть демонстрация, которую вы можете использовать в качестве начала. Может быть, Родриго (@RRUZ) заглянет; Я думаю, что он неплохо разбирается в OpenGL IIRC.   -  person Ken White    schedule 24.05.2012
comment
Спасибо, на самом деле DoubleBuffered — это первое, что я попробовал, но безуспешно.   -  person Jerry Dodge    schedule 24.05.2012
comment
Я не говорил использовать DoubleBuffered, как это реализовано в TForm. Я предложил поискать пример OpenGL, делающий то же самое. :)   -  person Ken White    schedule 24.05.2012
comment
Ааа... Я понимаю, что вы имеете в виду, да, я использую этот метод во многих своих элементах управления, просто не сделал этого в этом. Хорошая идея, но не думайте, что это фактическая проблема. Происходит что-то, что, кажется, очищает изображение сразу после того, как оно нарисовано.   -  person Jerry Dodge    schedule 24.05.2012
comment
Можно ли получить копию исходника? Я чувствую, что часть потока теряется при перемещении его сюда.   -  person Brendan    schedule 24.05.2012
comment
Подождите, вы правильно настраиваете gluLookAt?   -  person Brendan    schedule 24.05.2012
comment
Первоисточник: sendspace.com/file/5lhqdi — я разместил его ранее в другом вопросе ( удален), но, очевидно, было неправильным размещать ссылки на внешние источники, что и побудило меня сделать эту замыленную версию выше.   -  person Jerry Dodge    schedule 24.05.2012
comment
Я никогда не ссылаюсь на NeHe, но, по крайней мере, у него есть порты его туториалов на Delphi: nehe.gamedev .net/tutorial/your_first_polygon/13002 . Извините, я не могу больше помочь, я ничего не знаю о Delphi.   -  person Calvin1602    schedule 25.05.2012
comment
Новый вопрос об использовании OpenGL внутри потока: stackoverflow.com/questions/10746616/   -  person Jerry Dodge    schedule 25.05.2012
comment
Ответ обнаружен, огромная ошибка, пожалуйста, смотрите мой ответ ниже.   -  person Jerry Dodge    schedule 25.05.2012


Ответы (2)


glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glEnable(GL_NORMALIZE);
glShadeModel(GL_FLAT);
glCullFace(GL_BACK);
glLoadIdentity;
glPushMatrix();
glCallList(DL);
glPopMatrix();
SwapBuffers(wglGetCurrentDC);

Сначала добавьте () к вызову glLoadIdentity

Вы загружаете матрицу идентичности, затем нажимаете ее, затем визуализируете и выталкиваете ее, фактически не выполняя никаких преобразований.

Либо удалить 3 строки

glLoadIdentity;
glPushMatrix();

glPopMatrix();

или переместите glPushMatrix() на строку перед glLoadIdentity()

Я бы также предложил отключить затенение и отбраковку, чтобы убедиться, что они не мешают. Четырехугольники, объявленные в немного неправильном порядке с отбраковкой задней поверхности, просто отбраковывают их, даже если вы не хотите, чтобы они отбрасывались спереди.

person Brendan    schedule 24.05.2012
comment
Ага. :) Комментарий удален. Отредактировано на ‹strike›strike out‹/strike› примечание о добавлении (); круглые скобки необязательны в Delphi, если параметры не требуются. - person Ken White; 24.05.2012
comment
Легче сказать, чем сделать, учитывая исходный код — glLoadIdentity находится в одном месте, а два других — в совершенно другом, на самом деле это другой модуль. Исходный код на самом деле делает гораздо больше после glLoadIdentity и между двумя другими. Я пытаюсь переместить эти строки кода, чтобы увидеть результаты..... - person Jerry Dodge; 24.05.2012
comment
Пробовал это, и это не сработало, теперь даже не отображается рисунок. Я добавил оригинальный код, чтобы продемонстрировать, как на самом деле работает этот элемент управления. Процедура Draw каждого элемента рисует отдельный объект на сцене. - person Jerry Dodge; 24.05.2012

Проблема с мерцанием была обнаружена, и это была очень большая ошибка, и я не ожидаю, что вы разберетесь, не имея всего проекта. Проблема была в том, что я создавал ДВА таких элементов управления, и они мешали/боролись друг с другом. На самом деле, поскольку OpenGL работает по принципу «поток за потоком» (или в одном «контексте»), наличие двух разных элементов управления, пытающихся выполнить собственное рисование, будет мешать, что приведет к тому, что одно и то же произойдет на обоих элементах управления одновременно. . Так что перепрошивка была от того, что каждый контрол пытался по очереди делать отрисовку. Мой новый вопрос, который я задал, связанный с размещением этого рисунка внутри потока, прекрасно решит эту проблему. Это была глупая ошибка, и мне жаль тратить чье-то время.

person Jerry Dodge    schedule 25.05.2012