C++11 + SDL2 + Windows: Multithreaded program hangs after any input event - windows

I am working on a screen capture program using C++11, MinGW, and the Windows API. I am trying to use SDL2 to watch how my screen capture program works in real time.
The window opens fine, and the program seems to run well so long as I do nothing more than move the mouse cursor. But iff I click in the window, its menu bar, outside the window, or press any keys, the SDL window freezes.
I have set up some logging for the events to figure out what is happening. I never receive any events other than SDL_WINDOW_FOCUS_GAINED, SDL_TEXTEDITING, and SDL_WINDOWEVENT_SHOWN in that order. All of these are received at the start.
I have tried to find tutorials on SDL event handling since that's my best guess as to the source of the problem. I have found nothing more than basic event handling to watch for SDL_QUIT, basic mouse and keyboard events, and one on SDL_WINDOWEVENTs that does not seem to help. I have found nothing in-depth on what the events mean and best practices for handling them. That may not matter, because that might not be the source of the problem. For all I know, SDL is throwing a fit because there are other threads running.
Can anyone see any cause for this hanging in my code and provide an explanation as to how to fix it?
A quick explanation for the structure of my program is in order to cover the code I have omitted. The Captor class starts and runs a thread to grab a screenshot to pass to the Encoder. The Encoder starts a variable number of threads that receive a screenshot from the Captor, encode the screenshot, then passes the encoding to the Screen. The passing mechanism is the SynchronousQueue<T> class that provides paired methods put(const T&) and T get() to allow a producer and a consumer to synchronize using a resource; these methods time out to allow the the system to be responsive to kill messages.
Now for the source files (hopefully without too much bloat). While I would appreciate any comments on how to improve the performance of the application, my focus is on making the program responsive.
main.cpp
#include "RTSC.hpp"
int main(int argc, char** argv) {
RTSC rtsc {
(uint32_t) stoi(argv[1]),
(uint32_t) stoi(argv[2]),
(uint32_t) stoi(argv[3]),
(uint32_t) stoi(argv[4]),
(uint32_t) stoi(argv[5]),
(uint32_t) stoi(argv[6])
};
while (rtsc.isRunning()) {
SwitchToThread();
}
return 0;
}
RTSC.hpp
#ifndef RTSC_HPP
#define RTSC_HPP
#include "Captor.hpp"
#include "Encoder.hpp"
#include "Screen.hpp"
#include <iostream>
using namespace std;
class RTSC {
private:
Captor *captor;
Encoder *encoder;
SynchronousQueue<uint8_t*> imageQueue {1};
SynchronousQueue<RegionList> regionQueue {1};
Screen *screen;
public:
RTSC(
uint32_t width,
uint32_t height,
uint32_t maxRegionCount,
uint32_t threadCount,
uint32_t divisionsAlongThreadWidth,
uint32_t divisionsAlongThreadHeight
) {
captor = new Captor(width, height, imageQueue);
encoder = new Encoder(
width,
height,
maxRegionCount,
threadCount,
divisionsAlongThreadWidth,
divisionsAlongThreadHeight,
imageQueue,
regionQueue
);
screen = new Screen(
width,
height,
width >> 1,
height >> 1,
regionQueue
);
captor->start();
}
~RTSC() {
delete screen;
delete encoder;
delete captor;
}
bool isRunning() const {
return screen->isRunning();
}
};
#endif
Screen.hpp
#ifndef SCREEN_HPP
#define SCREEN_HPP
#include <atomic>
#include <SDL.h>
#include <windows.h>
#include "Region.hpp"
#include "SynchronousQueue.hpp"
using namespace std;
class Screen {
private:
atomic_bool running {false};
HANDLE thread;
SynchronousQueue<RegionList>* inputQueue;
uint32_t inputHeight;
uint32_t inputWidth;
uint32_t screenHeight;
uint32_t screenWidth;
SDL_Renderer* renderer;
SDL_Surface* surface;
SDL_Texture* texture;
SDL_Window* window;
void run() {
SDL_Event event;
while (running) {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
running = false;
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE:
running = false;
break;
default:
break;
}
}
try {
RegionList rl = inputQueue->get();
SDL_RenderClear(renderer);
SDL_LockSurface(surface);
SDL_FillRect(surface, nullptr, 0);
for (uint32_t i = 0; i < rl.count; ++i) {
Region &r = rl.regions[i];
SDL_Rect rect {
(int) r.getX(),
(int) r.getY(),
(int) r.getWidth(),
(int) r.getHeight()
};
uint32_t color =
(r.getRed() << 16) +
(r.getGreen() << 8) +
r.getBlue();
SDL_FillRect(surface, &rect, color);
}
SDL_UnlockSurface(surface);
SDL_UpdateTexture(
texture,
nullptr,
surface->pixels,
surface->pitch
);
SDL_RenderCopyEx(
renderer,
texture,
nullptr,
nullptr,
0,
nullptr,
SDL_FLIP_VERTICAL
);
} catch (exception &e) {}
SDL_RenderPresent(renderer);
SwitchToThread();
}
}
static DWORD startThread(LPVOID self) {
((Screen*) self)->run();
return (DWORD) 0;
}
public:
Screen(
uint32_t inputWidth,
uint32_t inputHeight,
uint32_t windowWidth,
uint32_t windowHeight,
SynchronousQueue<RegionList> &inputQueue
): inputQueue {&inputQueue}, inputHeight {inputHeight} {
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow(
"RTSC",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
windowWidth,
windowHeight,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE |
SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS
);
renderer = SDL_CreateRenderer(window, -1, 0);
surface = SDL_CreateRGBSurface(
0,
inputWidth,
inputHeight,
24,
0xFF << 16,
0xFF << 8,
0xFF,
0
);
texture = SDL_CreateTexture(
renderer,
surface->format->format,
SDL_TEXTUREACCESS_STREAMING,
inputWidth,
inputHeight
);
running = true;
thread = CreateThread(nullptr, 0, startThread, this, 0, nullptr);
}
~Screen() {
running = false;
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
SDL_FreeSurface(surface);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
bool isRunning() const {
return running;
}
};
#endif

I have no experience in using SDL API in a multithreaded environment but this isn't a big problem as you will see later. I've checked your code and there is at least one thing you should change in my opinion.
Generally, in case of GUI systems (and partly SDL is also a gui system) you should always access the gui only from the main thread and expect the gui events to come from the main thread. Most GUI APIs are single threaded and I wouldn't be surprised if this would apply to SDL too. Note that many gui systems run on the main thread of your process by default and you can't choose your own thread. Don't run the code of your Screen class on a worker thread, run it on your main thread and make EVERY SDL API call from the main thread.
If you are writing the game or a similar software then (first) write it as if it was single threaded. The subsystems of your engine (physics simulation, this-and-that-system, game logic, rendering) should be executed serially one-after-the-other on your main thread (from your main loop). If you want to make use of multithreading that do that in "another dimension": Convert some of the subsystems or a smaller unit of work (like merge sort) to multithreaded, for example a physics system tasks can often be split into several small tasks so when the physics system is updated by the main thread then the physics system can burn all of your cores...
Doing most of your tasks on the main thread has another advantage: It makes your code much more easy to port to any platform. Optionally if you write your code so that it can execute in single threaded mode then it can make debugging easier in many cases and then you also have a "reference build" to compare the multithreaded build to performancewise.

Related

How to get pixel data out of an IDXGISurface created with GPU access only?

In broad strokes, what I'm trying to accomplish is capture (part of) the screen and transform the capture into a digital image format. The following steps outline what I believe to be the solution:
Set up a Direct3D11CaptureFramePool and subscribe to its FrameArrived event
Gain access to the pixel data in the FrameArrived event delegate
Pass image data into the Windows Imaging Component to do the encoding
My issue is with step 2: While I can get the captured frame, gaining CPU read access to the surface fails. This my FrameArrived event delegate implementation (full repro below):
void on_frame_arrived(Direct3D11CaptureFramePool const& frame_pool, winrt::Windows::Foundation::IInspectable const&)
{
if (auto const frame = frame_pool.TryGetNextFrame())
{
if (auto const surface = frame.Surface())
{
if (auto const interop = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>())
{
com_ptr<IDXGISurface> dxgi_surface { nullptr };
check_hresult(interop->GetInterface(IID_PPV_ARGS(&dxgi_surface)));
DXGI_MAPPED_RECT info = {};
// Fails with `E_INVALIDARG`
check_hresult(dxgi_surface->Map(&info, DXGI_MAP_READ));
}
}
}
}
The Map() call is failing with E_INVALIDARG, and the debug layer offers additional, helpful error diagnostics:
DXGI ERROR: IDXGISurface::Map: This object was not created with CPUAccess flags that allow CPU access. [ MISCELLANEOUS ERROR #42: ]
So, now that I know what's wrong, how do I solve this? Specifically, how do I pull the pixel data out of a surface created with GPU access only?
Following is a full repro. It was originally created using the "Windows Console Application (C++/WinRT)" project template. The only change applied is "Precompiled Header: Use (/Yu)" → "Precompiled Header: Not Using Precompiled Headers", to keep this a single file.
It creates a command line application that expects a window handle as its only argument, in decimal, hex, or octal.
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Graphics.Capture.h>
#include <winrt/Windows.Graphics.DirectX.Direct3D11.h>
#include <winrt/Windows.Graphics.DirectX.h>
#include <Windows.Graphics.Capture.Interop.h>
#include <windows.graphics.capture.h>
#include <windows.graphics.directx.direct3d11.interop.h>
#include <Windows.h>
#include <d3d11.h>
#include <dxgi.h>
#include <cstdint>
#include <stdio.h>
#include <string>
using namespace winrt;
using namespace winrt::Windows::Graphics::Capture;
using namespace winrt::Windows::Graphics::DirectX;
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
void on_frame_arrived(Direct3D11CaptureFramePool const& frame_pool, winrt::Windows::Foundation::IInspectable const&)
{
wprintf(L"Frame arrived.\n");
if (auto const frame = frame_pool.TryGetNextFrame())
{
if (auto const surface = frame.Surface())
{
if (auto const interop = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>())
{
com_ptr<IDXGISurface> dxgi_surface { nullptr };
check_hresult(interop->GetInterface(IID_PPV_ARGS(&dxgi_surface)));
DXGI_MAPPED_RECT info = {};
// This is failing with `E_INVALIDARG`
check_hresult(dxgi_surface->Map(&info, DXGI_MAP_READ));
}
}
}
}
int wmain(int argc, wchar_t const* argv[])
{
init_apartment(apartment_type::single_threaded);
// Validate input
if (argc != 2)
{
wprintf(L"Usage: %s <HWND>\n", argv[0]);
return 1;
}
auto const target = reinterpret_cast<HWND>(static_cast<intptr_t>(std::stoi(argv[1], nullptr, 0)));
// Get `GraphicsCaptureItem` for `HWND`
auto interop = get_activation_factory<GraphicsCaptureItem, IGraphicsCaptureItemInterop>();
::ABI::Windows::Graphics::Capture::IGraphicsCaptureItem* capture_item_abi { nullptr };
check_hresult(interop->CreateForWindow(target, IID_PPV_ARGS(&capture_item_abi)));
// Move raw pointer into smart pointer
GraphicsCaptureItem const capture_item { capture_item_abi, take_ownership_from_abi };
// Create D3D device and request the `IDXGIDevice` interface...
com_ptr<ID3D11Device> device = { nullptr };
check_hresult(::D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG, nullptr, 0,
D3D11_SDK_VERSION, device.put(), nullptr, nullptr));
auto dxgi_device = device.as<IDXGIDevice>();
// ... so that we can get an `IDirect3DDevice` (the capture frame pool
// speaks WinRT only)
com_ptr<IInspectable> d3d_device_interop { nullptr };
check_hresult(::CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.get(), d3d_device_interop.put()));
auto d3d_device = d3d_device_interop.as<IDirect3DDevice>();
// Create a capture frame pool and capture session
auto const pool = Direct3D11CaptureFramePool::Create(d3d_device, DirectXPixelFormat::B8G8R8A8UIntNormalized, 1,
capture_item.Size());
auto const session = pool.CreateCaptureSession(capture_item);
[[maybe_unused]] auto const event_guard = pool.FrameArrived(auto_revoke, &on_frame_arrived);
// Start capturing
session.StartCapture();
// Have the system spin up a message loop for us
::MessageBoxW(nullptr, L"Stop capturing", L"Capturing...", MB_OK);
}
You must create a 2D texture that can be accessed by the CPU and copy the source frame into this 2D texture, which you can then Map. For example:
void on_frame_arrived(Direct3D11CaptureFramePool const& frame_pool, winrt::Windows::Foundation::IInspectable const&)
{
wprintf(L"Frame arrived.\n");
if (auto const frame = frame_pool.TryGetNextFrame())
{
if (auto const surface = frame.Surface())
{
if (auto const interop = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>())
{
com_ptr<IDXGISurface> surface;
check_hresult(interop->GetInterface(IID_PPV_ARGS(&surface)));
// get surface dimensions
DXGI_SURFACE_DESC desc;
check_hresult(surface->GetDesc(&desc));
// create a CPU-readable texture
// note: for max perf, the texture creation
// should be done once per surface size
// or allocate a big enough texture (like adapter-sized) and copy portions
D3D11_TEXTURE2D_DESC texDesc{};
texDesc.Width = desc.Width;
texDesc.Height = desc.Height;
texDesc.ArraySize = 1;
texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
texDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
texDesc.MipLevels = 1;
texDesc.SampleDesc.Count = 1;
texDesc.Usage = D3D11_USAGE_STAGING;
com_ptr<ID3D11Device> device;
check_hresult(surface->GetDevice(IID_PPV_ARGS(&device))); // or get the one from D3D11CreateDevice
com_ptr<ID3D11Texture2D> tex;
check_hresult(device->CreateTexture2D(&texDesc, nullptr, tex.put()));
com_ptr<ID3D11Resource> input;
check_hresult(interop->GetInterface(IID_PPV_ARGS(&input)));
com_ptr<ID3D11DeviceContext> dc;
device->GetImmediateContext(dc.put()); // or get the one from D3D11CreateDevice
// copy frame into CPU-readable resource
// this and the Map call can be done at each frame
dc->CopyResource(tex.get(), input.get());
D3D11_MAPPED_SUBRESOURCE map;
check_hresult(dc->Map(tex.get(), 0, D3D11_MAP_READ, 0, &map));
// TODO do something with map
dc->Unmap(tex.get(), 0);
}
}
}
}

How to programmatically start an application on a specific monitor on Windows 10?

I want to write a program that needs sometimes to start processes of another applications (mainly Sumatra PDF) on Windows 10, version 1803 (April 2018 Update).
These applications should be started on a specific monitor. I also want to be able to close the processes when needed.
The preferred languages are C# and Java, but any help is appreciated.
EDIT
I've tried to use the ShellExecuteExW function suggested by IInspectable in C++ code directly, but it doesn't work, as applications appear on the main monitor. I must have certainly made a mistake as I am absolutely new to WinAPI and know very little C++.
#include <Windows.h>
HMONITOR monitors[2]; // As it's only a test and I have currently only two monitors.
int monitorsCount = 0;
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lparam)
{
monitors[monitorsCount] = hMonitor;
monitorsCount++;
return TRUE;
}
int main()
{
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, 0);
_SHELLEXECUTEINFOW info;
ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_HMONITOR;
//info.lpVerb = L"open";
info.lpFile = L"C:\\Windows\\System32\\cmd.exe";
info.nShow = SW_SHOW;
info.hMonitor = monitors[1]; // Trying to start on the second monitor.
ShellExecuteExW(&info);
return 0;
}
As suggested by others, this is intended behavior of Windows and for the good reasons.
Also, you can not rely on default window placement atleast for SumatraPDF, since it surely does not use CW_USEDEFAULT, and instead stores these values in :
%USERPROFILE%\AppData\Roaming\SumatraPDF\SumatraPDF-settings.txt
There are multiple options though:
Use third party tools that monitor top level windows and based on pre-configured rules moves them to specified display. E.g. DisplayFusion, etc.
Use ligher weight solutions like AutoHotkey/AutoIt.
Try and do this in code itself. Following is a working solution. I smoke tested on my box.
Disclaimer: I have not written the entire code, for saving time I pulled it up from couple of sources, tweaked it, glued it together, and tested with SumantraPDF. Do also note, that this code is not of highest standards, but solves your problem, and will act as a can-be-done example.
C++ code: (scroll down for C# code)
#include <Windows.h>
#include <vector>
// 0 based index for preferred monitor
static const int PREFERRED_MONITOR = 1;
struct ProcessWindowsInfo
{
DWORD ProcessID;
std::vector<HWND> Windows;
ProcessWindowsInfo(DWORD const AProcessID)
: ProcessID(AProcessID)
{
}
};
struct MonitorInfo
{
HMONITOR hMonitor;
RECT rect;
};
BOOL WINAPI EnumProcessWindowsProc(HWND hwnd, LPARAM lParam)
{
ProcessWindowsInfo *info = reinterpret_cast<ProcessWindowsInfo*>(lParam);
DWORD WindowProcessID;
GetWindowThreadProcessId(hwnd, &WindowProcessID);
if (WindowProcessID == info->ProcessID)
{
if (GetWindow(hwnd, GW_OWNER) == (HWND)0 && IsWindowVisible(hwnd))
{
info->Windows.push_back(hwnd);
}
}
return true;
}
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lParam)
{
std::vector<MonitorInfo> *info = reinterpret_cast<std::vector<MonitorInfo>*>(lParam);
MonitorInfo monitorInfo = { 0 };
monitorInfo.hMonitor = hMonitor;
monitorInfo.rect = *lprect;
info->push_back(monitorInfo);
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
// NOTE: for now this code works only when the window is not already visible
// could be easily modified to terminate existing process as required
SHELLEXECUTEINFO info = { 0 };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_NOCLOSEPROCESS;
info.lpFile = L"C:\\Program Files\\SumatraPDF\\SumatraPDF.exe";
info.nShow = SW_SHOW;
std::vector<MonitorInfo> connectedMonitors;
// Get all available displays
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, reinterpret_cast<LPARAM>(&connectedMonitors));
if (ShellExecuteEx(&info))
{
WaitForInputIdle(info.hProcess, INFINITE);
ProcessWindowsInfo Info(GetProcessId(info.hProcess));
// Go though all windows from that process
EnumWindows((WNDENUMPROC)EnumProcessWindowsProc, reinterpret_cast<LPARAM>(&Info.ProcessID));
if (Info.Windows.size() == 1)
{
// only if we got at most 1 window
// NOTE: applications can have more than 1 top level window. But at least for SumtraPDF this works!
if (connectedMonitors.size() >= PREFERRED_MONITOR)
{
// only move the window if we were able to successfully detect available monitors
SetWindowPos(Info.Windows.at(0), 0, connectedMonitors.at(PREFERRED_MONITOR).rect.left, connectedMonitors.at(PREFERRED_MONITOR).rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
CloseHandle(info.hProcess);
}
return 0;
}
To emphasize one of my comments in code. This code will only work if the process in question is not already running. You can tweak the code as per your requirements otherwise.
Update: Added C# code below, as I realized OP prefers C#. This code also has the termination logic cooked in.
C# code:
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int PREFERRED_MONITOR = 1;
static void Main(string[] args)
{
// NOTE: you will have to reference System.Windows.Forms and System.Drawing (or
// equivalent WPF assemblies) for Screen and Rectangle
// Terminate existing SumatraPDF process, else we will not get the MainWindowHandle by following method.
List<Process> existingProcesses = Process.GetProcessesByName("SumatraPDF").ToList();
foreach (var existingProcess in existingProcesses)
{
// Ouch! Ruthlessly kill the existing SumatraPDF instances
existingProcess.Kill();
}
// Start the new instance of SumantraPDF
Process process = Process.Start(#"C:\Program Files\SumatraPDF\SumatraPDF.exe");
// wait max 5 seconds for process to be active
process.WaitForInputIdle(5000);
if (Screen.AllScreens.Length >= PREFERRED_MONITOR)
{
SetWindowPos(process.MainWindowHandle,
IntPtr.Zero,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Left,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Top,
0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
SEE_MASK_HMONITOR is only a request. Applications can choose their own window placement. SEE_MASK_HMONITOR only works when the executed application relies on default window placement, i.e. it creates its first top-level window with CW_USE­DEFAULT
So what you want is not generally possible. Your code is as good as you can get, if you don't control the launched application.

wglCreateContext throws INVALID_OPERATION exception

suppose that I have COpenGLControl class downloaded here from codeguru assuming that the first event handler runned when creating the OpenGL window is OnCreate, I have tried to catch up the errors of this class. here's the code used to create the window in my dialog's .h and .cpp files:
MyOpenGLTestDlg.h
COpenGLControl m_oglWindow;
MyOpenGLTestDlg.cpp
CRect rect;
// Get size and position of the picture control
GetDlgItem(ID_OPENGL)->GetWindowRect(rect);
// Convert screen coordinates to client coordinates
ScreenToClient(rect);
up to know I think the function OnCreate has been invoked. In fact, I think the line of code COpenGLControl m_oglWindow; causes this function to be invoked! but I'm not sure so it will be appreciated if you guide me alittle about this?
anyway, I have not made a lot of change to the class:
OpenGLControl.h
#pragma once
#include "afxwin.h"
#include "WinBase.h"
#include <gl/gl.h>
#include <gl/glu.h>
class COpenGLControl : public CWnd
{
public:
/******************/
/* Public Members */
/******************/
UINT_PTR m_unpTimer;
// View information variables
float m_fLastX;
float m_fLastY;
float m_fPosX;
float m_fPosY;
float m_fZoom;
float m_fRotX;
float m_fRotY;
bool m_bIsMaximized;
private:
/*******************/
/* Private Members */
/*******************/
// Window information
CWnd *hWnd; //window handle
HDC hdc; //device context handle
HGLRC hrc; //handle to GL Rendering Context
int m_nPixelFormat;
CRect m_rect;
CRect m_oldWindow;
CRect m_originalRect;
public:
COpenGLControl(void);
virtual ~COpenGLControl(void);
void oglCreate(CRect rect, CWnd *parent);
void oglInitialize(void);
void oglDrawScene(void);
// Added message classes:
afx_msg void OnPaint();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnDraw(CDC *pDC);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
};
OpenGLControl.cpp
int COpenGLControl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;
oglInitialize();
return 0;
}
void COpenGLControl::oglInitialize(void)
{
// Initial Setup:
//
static PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32, // bit depth
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24, // z-buffer depth
8,0,PFD_MAIN_PLANE, 0, 0, 0, 0,
};
// Get device context only once.
hdc = GetDC()->m_hDC;
// Pixel format.
m_nPixelFormat = ChoosePixelFormat(hdc, &pfd);
SetPixelFormat(hdc, m_nPixelFormat, &pfd);
// Create the OpenGL Rendering Context.
hrc = wglCreateContext(hdc);
GLenum error13 = glGetError();
wglMakeCurrent(hdc, hrc);
// Basic Setup:
//
// Set color to use when clearing the background.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearDepth(1.0f);
// Turn on backface culling
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
// Turn on depth testing
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
// Send draw request
OnDraw(NULL);
}
As you see I have written the code GLenum error13 = glGetError(); right after hrc = wglCreateContext(hdc); to catch any probable error that it throws and yes the value for error13 is 1282 which means INVALID_OPERATION so I think the handle for OpenGL rendering context is not created properly!
besides if you check the values for hdc and hrc, you encounter these:
hdc -> unused = ??? (Error: expression can not be evaluated)
hrc -> unused = 0
Could you help me find out why it is the case? and what's the problem?
The results are quite undefined if you call GL functions (like glGetError() itself) without having a current GL context.
wglCreateContext() is part of the Win33 API (and not the GL API) and will signal errors by returning a NULL pointer. You can call the Windows API function GetLastError() if you want details in that case, like with most other Windows API functions.
Do not call an OpenGL API function before you have a "current context" for the calling thread in Win32. The operations will be undefined.
To correct this issue, simply move the call to wglMakeCurrent (...) one line up so that it comes before the call to glGetError (...).
Note that you have to do this for every thread, and in Win32 only one thread is allowed to access an OpenGL context at any given time. If you ever want to do multi-threaded rendering in Win32, you will have to either acquire/release the context and do synchronization between threads or use a bunch of contexts that "share lists" (wglShareLists (...)).
Like derhass mentioned, if you want information about an error generated by the WGL API, use GetLastError (...). WGL is part of the window system, which is built on top of the Win32 API, so it will communicate its errors to you through the traditional Win32 channels.
By the way, try not to print GL error values in decimal form. They are always enumerated in gl.h as hexadecimal constants, so it would be easier to find the appropriate enumerant if you did this:
printf ("OpenGL Error: 0x%X\n", err);
The same goes for all enumerated constants in OpenGL. If you do not have a function that will map them to a human-readable string, you should use the hexadecimal value to look them up.

Windows + Qt and how to capture webcam feed without OpenCV

I'm fighting with this problem from a long time.
I can't get OpenCV to work, and I have follow a lot of tutorials about it and how to use in Qt, so I get tired and I want to avoid the use of OpenCV for this.
Now, my requirement or question... I need to show a webcam feed (real time video, without audio) in a Qt GUI application with only one button: "Take Snapshot" which, obviusly, take a picture from the current feed and store it.
That's all.
Is there anyway to get this done without using OpenCV ?
System specification:
Qt 4.8
Windows XP 32 bits
USB 2.0.1.3M UVC WebCam (the one I'm using now, it should support other models too)
Hope anybody can help me with this because I'm getting crazy.
Thanks in advance!
Ok, I finally did it, so I will post here my solution so we have something clear about this.
I used a library called 'ESCAPI': http://sol.gfxile.net/escapi/index.html
This provide a extremely easy way to capture frames from the device. With this raw data, I just create a QImage which later show in a QLabel.
I created a simple object to handle this.
#include <QDebug>
#include "camera.h"
Camera::Camera(int width, int height, QObject *parent) :
QObject(parent),
width_(width),
height_(height)
{
capture_.mWidth = width;
capture_.mHeight = height;
capture_.mTargetBuf = new int[width * height];
int devices = setupESCAPI();
if (devices == 0)
{
qDebug() << "[Camera] ESCAPI initialization failure or no devices found";
}
}
Camera::~Camera()
{
deinitCapture(0);
}
int Camera::initialize()
{
if (initCapture(0, &capture_) == 0)
{
qDebug() << "[Camera] Capture failed - device may already be in use";
return -2;
}
return 0;
}
void Camera::deinitialize()
{
deinitCapture(0);
}
int Camera::capture()
{
doCapture(0);
while(isCaptureDone(0) == 0);
image_ = QImage(width_, height_, QImage::Format_ARGB32);
for(int y(0); y < height_; ++y)
{
for(int x(0); x < width_; ++x)
{
int index(y * width_ + x);
image_.setPixel(x, y, capture_.mTargetBuf[index]);
}
}
return 1;
}
And the header file:
#ifndef CAMERA_H
#define CAMERA_H
#include <QObject>
#include <QImage>
#include "escapi.h"
class Camera : public QObject
{
Q_OBJECT
public:
explicit Camera(int width, int height, QObject *parent = 0);
~Camera();
int initialize();
void deinitialize();
int capture();
const QImage& getImage() const { return image_; }
const int* getImageRaw() const { return capture_.mTargetBuf; }
private:
int width_;
int height_;
struct SimpleCapParams capture_;
QImage image_;
};
#endif // CAMERA_H
It's so simple, but just for example purposes.
The use should be something like:
Camera cam(320, 240);
cam.initialize();
cam.capture();
QImage img(cam.getImage());
ui->label->setPixmap(QPixmap::fromImage(img));
Of course, you can use a QTimer and update the frame in QLabel and you will have video there...
Hope it help! and thanks Nicholas for your help!

Strange Results with ChangeDisplaySettings and Intel graphics card

I've been getting weird results with ChangeDisplaySettings and my Intel Graphics card. I find that when I try to go fullscreen, it won't do it in the same way as other programs.
My Intel control panel lets me handle different resolutions in 3 ways: Stretching the image, using the original resolution but centering the image, or Maintaining the aspect ratio with letterboxing. I set the default to maintain the aspect ratio, and some old games on my computer end up doing that. However, my program won't do the same. Instead, it gets centered.
Here's the code I'm using:
#include "windows.h"
DEVMODE DevMode;
DEVMODE UsedDevMode;
struct stOptions
{
char szFiles[260];
int xres;
int yres;
int bpp;
bool bMultiMon;
};
stOptions options;
void ApplyOptions(HWND hWnd)
{
int iModeNum=0;
bool bRes, bBpp, bFreq;
bRes=bBpp=bFreq=false;
bool bResult=true;
bool bChanged=false;
int iFreq;
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DevMode); //Get the current frequency
iFreq=DevMode.dmDisplayFrequency;
//With this, I try to find a DevMode that will work for the display. If it can't match all of the user's
//preferences, it will at least try to match as much as it can.
while (bResult)
{
bResult=EnumDisplaySettings(NULL, iModeNum, &DevMode);
if ((DevMode.dmPelsWidth==options.xres)&&(DevMode.dmPelsHeight==options.yres))
{
if (!bRes) EnumDisplaySettings(NULL, iModeNum, &UsedDevMode);
bRes=true; bChanged=true;
if (DevMode.dmBitsPerPel==options.bpp)
{
if (!bBpp) EnumDisplaySettings(NULL, iModeNum, &UsedDevMode);
bBpp=true;
if (DevMode.dmDisplayFrequency==iFreq)
{
EnumDisplaySettings(NULL, iModeNum, &UsedDevMode);
bFreq=true;
break;
}
}
}
iModeNum++;
}
if (!bChanged)
{
//TODO: add error handling
}
ChangeDisplaySettings(&UsedDevMode, CDS_FULLSCREEN);
MoveWindow(hWnd, 0, 0, options.xres, options.yres, true);
}
I'd like to know if anyone else with an intel card has this problem.
Thanks in advance!
I tried a simpler function and it more like I expected this time:
void ApplyOptions(HWND hWnd)
{
DEVMODE dmScreenSettings; // Device Mode
ZeroMemory (&dmScreenSettings, sizeof (DEVMODE)); // Make Sure Memory Is Cleared
dmScreenSettings.dmSize = sizeof (DEVMODE); // Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth = options.xres; // Select Screen Width
dmScreenSettings.dmPelsHeight = options.yres; // Select Screen Height
dmScreenSettings.dmBitsPerPel = options.bpp; // Select Bits Per Pixel
dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
if (ChangeDisplaySettings (&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
MessageBox(NULL, "Fail", "Error", MB_ICONHAND);
}
//ChangeDisplaySettings(&UsedDevMode, CDS_FULLSCREEN);
MoveWindow(hWnd, 0, 0, options.xres, options.yres, true);
}
I still don't know why this would be any different, but I guess it has something to do with dmScreenSettings.dmFields.

Resources