Изначально проблема возникла при попытке в реальном режиме времени параллельно с проигрыванием звука из *.wav файла прокручивать по экрану соответствующий ему график. При этом есть два момента. Во-первых, для выигрыша в скорости при прокрутке уже построенный график просто смещался на канве простым копированием (например функцией CopyRect), а достраивались только новые точки. Во-вторых, операции непосредственного копирования на экран синхронизировались с разверткой монитора средствами DirectX.
Для обкатки на практике такого способа быстрого вывода графики был создан дополнительный проект, который условно можно назвать «Осцилографом». С помощью генератора псевдослучайных чисел генерируется новые точки, которые в реальном режиме времени выводятся на экран. Таким образом, график все время смещается вправо. Основная идея, как уже упоминалось выше – уже построенный график просто копируется вправо на канве, а на новое место слева достраивается новая точка. И т.д. Если такую операцию выполнять без синхронизации с разверткой монитора, то изображение будет дергаться, смыкаться и т.д. Основная причина – если обновление графика при копировании происходит, когда «луч» монитора находится на нем, то часть графика выше луча еще не будет сдвинута, а часть ниже луча – будет. Возникает эффект смещения, который визуально выливается в нехорошие вещи.
В моем вспомогательном проекте синхронизация с разверткой монитора выполнена на DirectX. При этом должен отметить, что стандартная для таких целей функция WaitForVerticalBlank не использовалась, так как забирает практически 100 процентов времени процессора (не известно на что!). Синхронизация выполнена с помощью функции GetScanLine – более подробно можете посмотреть внизу, я выложу код.
Основная идея такая. Есть две буферные поверхности (как я их назвал – активная и пассивная) и одна первичная. Есть два потока. Один поток осуществляет блиттинг с активной поверхности на первичную, а также блиттинг со смещением с активной поверхности на пассивную. Второй поток просто достраивает на пассивную поверхность новую точку. Эти операции длятся на протяжении одного кадра. Потом активная поверхность меняется местами с пассивной и т.д. Блиттинг осуществляется не всей поверхности сразу, а отслеживает в цикле положение луча и блиттится полосками над лучем. Это сложно объяснить словами, кому интересно – лучше в коде посмотрите.
Приведенный ниже код является полностью рабочим и удовлетворяет всем требованиям, которые выдвигались вначале разработки. НО С ОДНИМ НО (в котором, собственно, и заключается проблема и из-за которого я и пишу данный пост). График движется абсолютно нормально, без дерганий и мерцаний, четко виден и т.п. на трубчатых мониторах. При этом желательно конечно, чтобы компьютер был довольно мощным (процессор от 2 ГГц), либо же не раскрывайте окно на весь экран. Проблема возникает на жидко-кристаллических мониторах. График как и раньше не дергается, но при этом линии графика при смещении выглядят размытыми. При чем чем больше смещение на одну точку, тем более размытыми выглядят линии графика. В этом Вы сможете убедиться самостоятельно, так как в программе предусмотрена опция изменения величины смещения. Я акцентирую внимание: при прочих равных условиях (тот же самый компьютер, та же самая программа, то же разрешение монитора и частота обновления) на трубчатом мониторе все класно, а на TFT график выглядит размытым. В чем может быть причина такого эффекта? На сколько я понимаю, ТФТ монитор иначе, чем трубчатый, обновляет свой экран. Я, конечно же, понимаю, что такое понятие, как «луч» в случае жидко кристаллических мониторов вырождается, но тем не менее в документации к DirectX я не встречал упоминаний, что данная функция относится лишь к какому то виду мониторов или что этот код надо писать так-то для таких мониторов, и так-то для таких.
У меня возникает куча вопросов по тому, как именно ТФТ монитор обновляет свой экран. Я искал какую-нибудь литературу на эту тему как в книжках, так и в Инете, но ничего не нашел. Может кто-нибудь что-нибудь встречал, киньте пожалуйста линк или отошлите мне на почту volkovych(собака)gmail.com. Например, ТФТ обновляет экран строчка за строчкой сверху вниз или же по мере поступления данных и необходимости обновить ту или иную часть экрана? Он обновляет экран со стабильной частотой и весь, или же по мере поступления данных и только те части, которые изменились? Он изменяет в данный момент лишь какой-то один кусок данных, или же может обновлять сразу две области? Ответы на эти вопросы дали бы пищу для дальнейших рассуждений.
И еще. Мерцание на ТФТ-мониторах, про которое я писал, наблюдается как при DVI, так и при VGA-подключении. Кстати, я слышал, что вроде бы DVI имеет свою собственную синхронизацию, кто-нибудь встречал инфу на эту тему?
Исполняемый файл можно закачать здесь (489 кБ):
http://rapidshare.de/files/28504278/Project1.exe.html
Вот код:
Заголовочный файл
Цитата:
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TGroupBox *GroupBox2;
TEdit *EditOffsetPerPoint;
TLabel *LabelOffsetPerPoint;
TButton *ButtonApplySettings;
TLabel *Label1;
void __fastcall FormDestroy(TObject *Sender);
void __fastcall ButtonApplySettingsClick(TObject *Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
float __fastcall Rando();
LPDIRECTDRAW lpDD;
LPDIRECTDRAWSURFACE lpPrimaryDDSurface, lpBackDDSurface[2];
LPDIRECTDRAWCLIPPER lpDDClipper;
bool RepaintOfPassiveBackDDSurfaceWasFinished;
DWORD ActiveBackDDSurface, PassiveBackDDSurface;
HANDLE RepaintBackDDSurface, RepaintBackDDSurfaceThread, UpdateScreenThread;
void __fastcall ApplySettings();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Файл реализации
Цитата:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <ddraw.h>
#include <mmsystem.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
TForm1 *Form1;
int d;
int dnew;
bool TerminateRepaintBackDDSurfaceThread = false;
bool TerminateUpdateScreenThread = false;
DWORD VerticalResolution, HorizontalResolution;
//Функиция потока прорисовки графиков
unsigned long WINAPI RepaintBackDDSurfaces(LPVOID param)
{
HDC PassiveBackDDSurfaceDC;
HPEN hpen;
double OldValue = 0, NewValue, GraphMin = 0, GraphMax = 1, gx, gy;
while (!TerminateRepaintBackDDSurfaceThread)
{
WaitForSingleObject(Form1->RepaintBackDDSurface, INFINITE);
Form1->lpBackDDSurface[Form1->PassiveBackDDSurface]->Blt(&Rect(d, 0, d + 1, Form1->Panel1->Top), Form1->lpBackDDSurface[Form1->ActiveBackDDSurface], &Rect(0, 0, 1, Form1->Panel1->Top), DDBLT_WAIT, NULL);
Form1->lpBackDDSurface[Form1->PassiveBackDDSurface]->GetDC(&PassiveBackDDSurfaceDC);
hpen = (HPEN)SelectObject(PassiveBackDDSurfaceDC, GetStockObject(WHITE_PEN));
Rectangle(PassiveBackDDSurfaceDC, 0, 0, d, Form1->Panel1->Top);
SelectObject(PassiveBackDDSurfaceDC, hpen);
//Получаем новую точку
NewValue = Form1->Rando();
gy = Form1->Panel1->Top - 1.0 * Form1->Panel1->Top*(OldValue-GraphMin)/(GraphMax-GraphMin);
gx = d;
MoveToEx(PassiveBackDDSurfaceDC, gx, gy, NULL);
//Рисуем линию графика
gy = Form1->Panel1->Top - 1.0 * Form1->Panel1->Top*(NewValue-GraphMin)/(GraphMax-GraphMin);
gx = 0;
LineTo(PassiveBackDDSurfaceDC, gx, gy);
OldValue = NewValue;
Form1->lpBackDDSurface[Form1->PassiveBackDDSurface]->ReleaseDC(PassiveBackDDSurfaceDC);
Form1->RepaintOfPassiveBackDDSurfaceWasFinished = true;
}
return(0);
}
//Функция потока обновление графика
unsigned long WINAPI UpdateScreen(LPVOID param)
{
bool PaintActiveBackDDSurface = false;
DWORD ScanLine, BeginLine, EndLine, LastPaintedLine, Dopom, OldScanLine = 0;
timeBeginPeriod(1); //Необходима для того, чтобы Sleep(1) действительно спал порядка 1-2 мс
DWORD BltXBegin, BltXEnd, BltYBegin, BltYEnd, ClientOriginYConst;
while (!TerminateUpdateScreenThread)
{
while (!TerminateUpdateScreenThread)
{
if (Form1->lpDD->GetScanLine(&ScanLine) == DDERR_VERTICALBLANKINPROGRESS || ScanLine < OldScanLine) break;
if (PaintActiveBackDDSurface && (ScanLine > BeginLine) && (LastPaintedLine < EndLine))
{
if (Form1->ClientOrigin.x >= 0 && Form1->ClientOrigin.x + Form1->ClientWidth - 1 <= HorizontalResolution - 1) {BltXBegin = 0; BltXEnd = Form1->ClientWidth - 1;}
else
{
if (Form1->ClientOrigin.x < 0) {BltXBegin = - Form1->ClientOrigin.x; BltXEnd = Form1->ClientWidth - 1;}
else {BltXBegin = 0; BltXEnd = HorizontalResolution - 1 - Form1->ClientOrigin.x;}
}
Form1->lpPrimaryDDSurface->Blt(&Rect(Form1->ClientOrigin.x + BltXBegin, LastPaintedLine, Form1->ClientOrigin.x + BltXEnd, min(ScanLine, EndLine)), Form1->lpBackDDSurface[Form1->ActiveBackDDSurface], &Rect(BltXBegin, LastPaintedLine - ClientOriginYConst, BltXEnd, min(ScanLine, EndLine) - ClientOriginYConst), DDBLT_WAIT, NULL); // != DD_OK) Application->MessageBox("Помилка при бліттінгу", "погано", MB_OK);
Form1->lpBackDDSurface[Form1->PassiveBackDDSurface]->Blt(&Rect(d + 1, LastPaintedLine - ClientOriginYConst, Form1->ClientWidth - 1, min(ScanLine, EndLine) - ClientOriginYConst), Form1->lpBackDDSurface[Form1->ActiveBackDDSurface], &Rect(1, LastPaintedLine - ClientOriginYConst, Form1->ClientWidth - 1 - d - 1 + 1, min(ScanLine, EndLine) - ClientOriginYConst), DDBLT_WAIT, NULL); // != DD_OK) Application->MessageBox("Помилка при бліттінгу", "погано", MB_OK);
LastPaintedLine = min(ScanLine, EndLine);
}
OldScanLine = ScanLine;
Sleep(1);
}
OldScanLine = 0;
if (PaintActiveBackDDSurface && LastPaintedLine < EndLine)
{
Form1->lpPrimaryDDSurface->Blt(&Rect(Form1->ClientOrigin.x + BltXBegin, LastPaintedLine, Form1->ClientOrigin.x + BltXEnd, EndLine), Form1->lpBackDDSurface[Form1->ActiveBackDDSurface], &Rect(BltXBegin, LastPaintedLine - ClientOriginYConst, BltXEnd, EndLine - ClientOriginYConst), DDBLT_WAIT, NULL);
Form1->lpBackDDSurface[Form1->PassiveBackDDSurface]->Blt(&Rect(d + 1, LastPaintedLine - ClientOriginYConst, Form1->ClientWidth - 1, EndLine - ClientOriginYConst), Form1->lpBackDDSurface[Form1->ActiveBackDDSurface], &Rect(1, LastPaintedLine - ClientOriginYConst, Form1->ClientWidth - 1 - d - 1 + 1, EndLine - ClientOriginYConst), DDBLT_WAIT, NULL);
LastPaintedLine = EndLine;
}
PaintActiveBackDDSurface = Form1->RepaintOfPassiveBackDDSurfaceWasFinished;
if (Form1->RepaintOfPassiveBackDDSurfaceWasFinished)
{
Dopom = Form1->ActiveBackDDSurface;
Form1->ActiveBackDDSurface = Form1->PassiveBackDDSurface;
Form1->PassiveBackDDSurface = Dopom;
Form1->RepaintOfPassiveBackDDSurfaceWasFinished = false;
SetEvent(Form1->RepaintBackDDSurface);
BeginLine = Form1->ClientOrigin.y;
EndLine = BeginLine + Form1->Panel1->Top;
ClientOriginYConst = Form1->ClientOrigin.y;
LastPaintedLine = BeginLine;
}
Form1->ApplySettings();
if (Form1->lpDD->GetScanLine(&ScanLine) == DDERR_VERTICALBLANKINPROGRESS) Sleep(1);
}
return(0);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
DDSURFACEDESC ddsd;
//Создание объекта DirectDraw
lpDD = NULL;
if (DirectDrawCreate(NULL, &lpDD, NULL) != DD_OK)
{
Application->MessageBox("Проблемы при создании DirectDraw!", "Плохо!", MB_OK);
return;
}
//Установление уровня кооперации для интерфейса DirectDraw
if (lpDD->SetCooperativeLevel(Handle, DDSCL_NORMAL) != DD_OK)
{
Application->MessageBox("Проблемы при установлении уровня кооперации!", "Плохо!", MB_OK);
return;
}
//Заполнение полей структуры DDSURFACEDESC для создания первичной поверхности
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
//Создание первичной поверхности
if (lpDD->CreateSurface(&ddsd, &lpPrimaryDDSurface, NULL) != DD_OK)
{
Application->MessageBox("Проблемы при создании первичной поверхности!", "Плохо!", MB_OK);
return;
}
//Создание объекта отсечения
if (lpDD->CreateClipper(NULL, &lpDDClipper, NULL) != DD_OK)
{
Application->MessageBox("Проблемы при создании объекта Clipper!", "Плохо!", MB_OK);
return;
}
//Связывание объекта отсечения с окном
if (lpDDClipper->SetHWnd(NULL, Handle) != DD_OK)
{
Application->MessageBox("Проблемы при привязке метки окна к Clipper'у!", "Плохо!", MB_OK);
return;
}
//Привязка объекта отсечение к первичной поверхности
if (lpPrimaryDDSurface->SetClipper(lpDDClipper) != DD_OK)
{
Application->MessageBox("Проблемы при привязке Clipper'a к первичной поверхности!", "Плохо!", MB_OK);
return;
}
//Определение параметров монитора
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
if (lpDD->GetDisplayMode(&ddsd) != DD_OK)
{
Application->MessageBox("Проблемы при определении режима монитора!", "Плохо!", MB_OK);
return;
}
VerticalResolution = ddsd.dwHeight;
HorizontalResolution = ddsd.dwWidth;
//Создание буферных поверхностей в видеопамяти
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
ddsd.dwWidth = HorizontalResolution;
ddsd.dwHeight = VerticalResolution;
if (lpDD->CreateSurface(&ddsd, &lpBackDDSurface[0], NULL) != DD_OK || lpDD->CreateSurface(&ddsd, &lpBackDDSurface[1], NULL) != DD_OK)
{
Application->MessageBox("Проблемы при создании буферных поверхностей в видеопамяти!", "Плохо!", MB_OK);
return;
}
ActiveBackDDSurface = 1;
PassiveBackDDSurface = 0;
RepaintOfPassiveBackDDSurfaceWasFinished = false;
//Определение настроек
ButtonApplySettingsClick(NULL);
ApplySettings();
RepaintBackDDSurface = CreateEvent(NULL, FALSE, TRUE, NULL);
RepaintBackDDSurfaceThread = CreateThread(NULL, NULL, RepaintBackDDSurfaces, NULL, CREATE_SUSPENDED, NULL);
SetThreadPriority(RepaintBackDDSurfaceThread, THREAD_PRIORITY_ABOVE_NORMAL);
ResumeThread(RepaintBackDDSurfaceThread);
UpdateScreenThread = CreateThread(NULL, NULL, UpdateScreen, NULL, CREATE_SUSPENDED, NULL);
SetThreadPriority(UpdateScreenThread, THREAD_PRIORITY_TIME_CRITICAL);
ResumeThread(UpdateScreenThread);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
TerminateUpdateScreenThread = true;
TerminateRepaintBackDDSurfaceThread = true;
Sleep(100);
if (lpDD != NULL)
{
if (lpPrimaryDDSurface != NULL)
{
if (lpDDClipper != NULL)
{
lpDDClipper->Release();
lpDDClipper = NULL;
}
lpPrimaryDDSurface->Release();
lpPrimaryDDSurface = NULL;
}
if (lpBackDDSurface[0] != NULL)
{
lpBackDDSurface[0]->Release();
lpBackDDSurface[0] = NULL;
}
if (lpBackDDSurface[1] != NULL)
{
lpBackDDSurface[1]->Release();
lpBackDDSurface[1] = NULL;
}
lpDD->Release();
lpDD = NULL;
}
}
//---------------------------------------------------------------------------
//Генератор псевдослучайных чисел в диапазоне 0 < r < 1
long in_rndm=1; //Начальное значение последовательности
float __fastcall TForm1::Rando()
{
static float r;
m2: in_rndm *= 331804469l;
r=in_rndm*0.232832e-9+0.5;
if(r >=1. || r <= 0.)goto m2;
return r;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ButtonApplySettingsClick(TObject *Sender)
{
dnew = StrToInt(EditOffsetPerPoint->Text);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ApplySettings()
{
d = dnew;
}
Буду благодарен за любую помощь!
ПС. В случае необходимости могу выложить и весь билдеровский проект.