2010-11-06 08:28:11 +02:00
|
|
|
/*
|
2018-05-09 05:09:57 +02:00
|
|
|
* Copyright (c) 2010 Toni Spets <toni.spets@iki.fi>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2013-06-26 11:19:12 +02:00
|
|
|
|
2010-11-06 08:28:11 +02:00
|
|
|
#include <windows.h>
|
|
|
|
#include <stdio.h>
|
2018-05-09 05:09:57 +02:00
|
|
|
#include "opengl.h"
|
2010-11-06 08:28:11 +02:00
|
|
|
#include "main.h"
|
|
|
|
#include "surface.h"
|
2018-05-20 06:27:53 +02:00
|
|
|
#include "paletteshader.h"
|
2018-05-09 05:09:57 +02:00
|
|
|
|
|
|
|
|
2011-03-24 19:41:36 +02:00
|
|
|
BOOL detect_cutscene();
|
2018-05-18 16:18:34 +02:00
|
|
|
DWORD WINAPI render_soft_main(void);
|
2011-03-24 19:41:36 +02:00
|
|
|
|
2010-11-13 10:49:46 +02:00
|
|
|
DWORD WINAPI render_main(void)
|
2010-11-06 08:28:11 +02:00
|
|
|
{
|
2018-05-10 14:45:39 +02:00
|
|
|
Sleep(500);
|
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
HGLRC hRC = wglCreateContext(ddraw->render.hDC);
|
2018-05-18 16:18:34 +02:00
|
|
|
BOOL madeCurrent = hRC && wglMakeCurrent(ddraw->render.hDC, hRC);
|
|
|
|
|
|
|
|
if (!madeCurrent || (ddraw->autorenderer && glGetError() != GL_NO_ERROR))
|
|
|
|
{
|
|
|
|
if (madeCurrent)
|
|
|
|
{
|
|
|
|
wglMakeCurrent(NULL, NULL);
|
|
|
|
wglDeleteContext(hRC);
|
|
|
|
}
|
|
|
|
|
|
|
|
ddraw->renderer = render_soft_main;
|
|
|
|
return render_soft_main();
|
|
|
|
}
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
OpenGL_Init();
|
2010-11-17 22:12:19 +02:00
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
if (OpenGL_ExtExists("WGL_EXT_swap_control"))
|
2010-11-17 22:12:19 +02:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
BOOL(APIENTRY *wglSwapIntervalEXT)(int) = (BOOL(APIENTRY *)(int))wglGetProcAddress("wglSwapIntervalEXT");
|
|
|
|
if (wglSwapIntervalEXT)
|
2010-11-17 22:12:19 +02:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
if (ddraw->vsync)
|
|
|
|
wglSwapIntervalEXT(1);
|
|
|
|
else
|
|
|
|
wglSwapIntervalEXT(0);
|
2010-11-17 22:12:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-11 18:58:58 +02:00
|
|
|
DWORD tick_start = 0;
|
|
|
|
DWORD tick_end = 0;
|
|
|
|
DWORD frame_len = 0;
|
2018-05-18 16:18:34 +02:00
|
|
|
int maxfps = ddraw->render.maxfps;
|
2010-11-06 21:30:48 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps < 0)
|
|
|
|
maxfps = ddraw->mode.dmDisplayFrequency;
|
2010-11-06 21:30:48 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps == 0)
|
|
|
|
maxfps = 125;
|
2018-05-10 14:57:12 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps >= 1000)
|
|
|
|
maxfps = 0;
|
2018-05-12 12:28:07 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps > 0)
|
|
|
|
frame_len = 1000.0f / maxfps;
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2018-05-13 11:15:22 +02:00
|
|
|
int tex_width =
|
|
|
|
ddraw->width <= 1024 ? 1024 : ddraw->width <= 2048 ? 2048 : ddraw->width <= 4096 ? 4096 : ddraw->width;
|
|
|
|
int tex_height =
|
|
|
|
ddraw->height <= tex_width ? tex_width : ddraw->height <= 2048 ? 2048 : ddraw->height <= 4096 ? 4096 : ddraw->height;
|
|
|
|
|
|
|
|
tex_width = tex_width > tex_height ? tex_width : tex_height;
|
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
float scale_w = (float)ddraw->width / tex_width;
|
|
|
|
float scale_h = (float)ddraw->height / tex_height;
|
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
int tex_size = tex_width * tex_height * sizeof(int);
|
|
|
|
int *tex = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, tex_size);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
BOOL got30 = glGenFramebuffers && glBindFramebuffer && glFramebufferTexture2D && glDrawBuffers &&
|
|
|
|
glCheckFramebufferStatus && glUniform4f && glActiveTexture && glUniform1i &&
|
|
|
|
glGetAttribLocation && glGenBuffers && glBindBuffer && glBufferData && glVertexAttribPointer &&
|
|
|
|
glEnableVertexAttribArray && glUniform2fv && glUniformMatrix4fv && glGenVertexArrays && glBindVertexArray;
|
|
|
|
|
|
|
|
BOOL got20 = glGetUniformLocation && glActiveTexture && glUniform1i;
|
|
|
|
|
2018-05-09 08:14:10 +02:00
|
|
|
GLuint paletteConvProgram = 0;
|
2018-05-20 06:27:53 +02:00
|
|
|
if (got30)
|
2018-05-12 12:28:07 +02:00
|
|
|
paletteConvProgram = OpenGL_BuildProgram(PassthroughVertShaderSrc, PaletteFragShaderSrc);
|
2018-05-20 06:27:53 +02:00
|
|
|
else if (got20)
|
|
|
|
paletteConvProgram = OpenGL_BuildProgram(PassthroughVertShader110Src, PaletteFragShader110Src);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
GLuint scaleProgram = 0;
|
2018-05-20 06:27:53 +02:00
|
|
|
if (got30)
|
2018-05-12 12:28:07 +02:00
|
|
|
scaleProgram = OpenGL_BuildProgramFromFile(ddraw->shader);
|
2018-05-09 05:09:57 +02:00
|
|
|
|
|
|
|
// primary surface texture
|
2018-05-09 05:56:44 +02:00
|
|
|
GLuint surfaceTexId = 0;
|
|
|
|
glGenTextures(1, &surfaceTexId);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, surfaceTexId);
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
|
2018-05-10 05:22:32 +02:00
|
|
|
while (glGetError() != GL_NO_ERROR);
|
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
if (paletteConvProgram)
|
2018-05-10 05:22:32 +02:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, tex_width, tex_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
|
2018-05-10 05:22:32 +02:00
|
|
|
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, tex_width, tex_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
|
2018-05-14 10:35:30 +02:00
|
|
|
|
|
|
|
if (glGetError() != GL_NO_ERROR)
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
|
2018-05-10 05:22:32 +02:00
|
|
|
}
|
2018-05-09 05:09:57 +02:00
|
|
|
else
|
2018-05-10 05:22:32 +02:00
|
|
|
{
|
2018-05-14 10:35:30 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
2018-05-10 05:22:32 +02:00
|
|
|
}
|
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
|
|
|
|
// palette texture
|
2018-05-09 05:56:44 +02:00
|
|
|
GLuint paletteTexId = 0;
|
|
|
|
glGenTextures(1, &paletteTexId);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, paletteTexId);
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
2018-05-13 11:15:22 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
2010-11-07 09:59:44 +02:00
|
|
|
|
2018-03-15 20:49:27 +01:00
|
|
|
glViewport(
|
2018-05-09 05:09:57 +02:00
|
|
|
ddraw->render.viewport.x, ddraw->render.viewport.y,
|
2018-03-15 20:49:27 +01:00
|
|
|
ddraw->render.viewport.width, ddraw->render.viewport.height);
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
|
2018-05-20 17:39:14 +02:00
|
|
|
GLint surfaceUniLoc = -1, paletteUniLoc = -1, mainTexCoordAttrLoc = -1, mainVertexCoordAttrLoc = -1;
|
2018-05-20 06:27:53 +02:00
|
|
|
GLuint mainVbos[3], mainVao;
|
2018-05-09 05:56:44 +02:00
|
|
|
if (paletteConvProgram)
|
2017-11-16 22:21:13 +01:00
|
|
|
{
|
2018-05-09 05:56:44 +02:00
|
|
|
surfaceUniLoc = glGetUniformLocation(paletteConvProgram, "SurfaceTex");
|
|
|
|
paletteUniLoc = glGetUniformLocation(paletteConvProgram, "PaletteTex");
|
2018-05-20 06:27:53 +02:00
|
|
|
|
|
|
|
if (got30)
|
|
|
|
{
|
|
|
|
glUseProgram(paletteConvProgram);
|
|
|
|
|
2018-05-20 17:39:14 +02:00
|
|
|
mainVertexCoordAttrLoc = glGetAttribLocation(paletteConvProgram, "VertexCoord");
|
2018-05-20 06:27:53 +02:00
|
|
|
mainTexCoordAttrLoc = glGetAttribLocation(paletteConvProgram, "TexCoord");
|
|
|
|
|
|
|
|
glGenBuffers(3, mainVbos);
|
|
|
|
|
|
|
|
if (scaleProgram)
|
|
|
|
{
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[0]);
|
2018-05-20 07:06:32 +02:00
|
|
|
static const GLfloat vertexCoord[] = {
|
2018-05-20 06:27:53 +02:00
|
|
|
-1.0f, -1.0f,
|
|
|
|
-1.0f, 1.0f,
|
|
|
|
1.0f, 1.0f,
|
|
|
|
1.0f, -1.0f,
|
|
|
|
};
|
2018-05-20 07:06:32 +02:00
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexCoord), vertexCoord, GL_STATIC_DRAW);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
|
|
|
GLfloat texCoord[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
0.0f, scale_h,
|
|
|
|
scale_w, scale_h,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
2018-05-20 06:27:53 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[0]);
|
2018-05-20 06:27:53 +02:00
|
|
|
static const GLfloat vertexCoord[] = {
|
|
|
|
-1.0f, 1.0f,
|
|
|
|
1.0f, 1.0f,
|
|
|
|
1.0f,-1.0f,
|
|
|
|
-1.0f,-1.0f,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexCoord), vertexCoord, GL_STATIC_DRAW);
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
|
|
|
GLfloat texCoord[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
scale_w, scale_h,
|
|
|
|
0.0f, scale_h,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
2018-05-20 06:27:53 +02:00
|
|
|
}
|
2018-05-20 07:06:32 +02:00
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glGenVertexArrays(1, &mainVao);
|
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[0]);
|
2018-05-20 17:39:14 +02:00
|
|
|
glVertexAttribPointer(mainVertexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainVertexCoordAttrLoc);
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
|
|
|
glVertexAttribPointer(mainTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mainVbos[2]);
|
|
|
|
static const GLushort indices[] =
|
|
|
|
{
|
|
|
|
0, 1, 2,
|
|
|
|
0, 2, 3,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
glUniform4f(glGetAttribLocation(paletteConvProgram, "Color"), 1, 1, 1, 1);
|
|
|
|
|
|
|
|
const float mvpMatrix[16] = {
|
|
|
|
1,0,0,0,
|
|
|
|
0,1,0,0,
|
|
|
|
0,0,1,0,
|
|
|
|
0,0,0,1,
|
|
|
|
};
|
|
|
|
glUniformMatrix4fv(glGetUniformLocation(paletteConvProgram, "MVPMatrix"), 1, GL_FALSE, mvpMatrix);
|
|
|
|
|
|
|
|
}
|
2017-11-16 22:21:13 +01:00
|
|
|
}
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
GLint textureUniLoc = -1, scaleTexCoordAttrLoc = -1, frameCountUniLoc = -1;
|
2018-05-12 12:28:07 +02:00
|
|
|
GLuint frameBufferId = 0;
|
|
|
|
GLuint frameBufferTexId = 0;
|
2018-05-20 06:27:53 +02:00
|
|
|
GLuint scaleVbos[3], scaleVao;
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
if (scaleProgram)
|
|
|
|
{
|
|
|
|
glUseProgram(scaleProgram);
|
|
|
|
|
2018-05-13 11:15:22 +02:00
|
|
|
GLint vertexCoordAttrLoc = glGetAttribLocation(scaleProgram, "VertexCoord");
|
2018-05-20 06:27:53 +02:00
|
|
|
scaleTexCoordAttrLoc = glGetAttribLocation(scaleProgram, "TexCoord");
|
2018-05-12 12:28:07 +02:00
|
|
|
textureUniLoc = glGetUniformLocation(scaleProgram, "Texture");
|
|
|
|
frameCountUniLoc = glGetUniformLocation(scaleProgram, "FrameCount");
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glGenBuffers(3, scaleVbos);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, scaleVbos[0]);
|
2018-05-12 12:28:07 +02:00
|
|
|
static const GLfloat vertexCoord[] = {
|
|
|
|
-1.0f, 1.0f,
|
|
|
|
1.0f, 1.0f,
|
|
|
|
1.0f,-1.0f,
|
|
|
|
-1.0f,-1.0f,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexCoord), vertexCoord, GL_STATIC_DRAW);
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, scaleVbos[1]);
|
|
|
|
GLfloat texCoord[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
scale_w, scale_h,
|
|
|
|
0.0f, scale_h,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glGenVertexArrays(1, &scaleVao);
|
|
|
|
glBindVertexArray(scaleVao);
|
|
|
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, scaleVbos[0]);
|
2018-05-13 11:15:22 +02:00
|
|
|
glVertexAttribPointer(vertexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(vertexCoordAttrLoc);
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, scaleVbos[1]);
|
|
|
|
glVertexAttribPointer(scaleTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(scaleTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, scaleVbos[2]);
|
2018-05-12 12:28:07 +02:00
|
|
|
static const GLushort indices[] =
|
|
|
|
{
|
|
|
|
0, 1, 2,
|
|
|
|
0, 2, 3,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindVertexArray(0);
|
2018-05-12 12:28:07 +02:00
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
float inputSize[2], outputSize[2], textureSize[2];
|
|
|
|
|
|
|
|
inputSize[0] = ddraw->width;
|
|
|
|
inputSize[1] = ddraw->height;
|
|
|
|
textureSize[0] = tex_width;
|
|
|
|
textureSize[1] = tex_height;
|
|
|
|
outputSize[0] = ddraw->render.viewport.width;
|
|
|
|
outputSize[1] = ddraw->render.viewport.height;
|
|
|
|
|
|
|
|
glUniform2fv(glGetUniformLocation(scaleProgram, "OutputSize"), 1, outputSize);
|
|
|
|
glUniform2fv(glGetUniformLocation(scaleProgram, "TextureSize"), 1, textureSize);
|
|
|
|
glUniform2fv(glGetUniformLocation(scaleProgram, "InputSize"), 1, inputSize);
|
|
|
|
|
|
|
|
glUniform4f(glGetAttribLocation(scaleProgram, "Color"), 1, 1, 1, 1);
|
|
|
|
glUniform1i(glGetUniformLocation(scaleProgram, "FrameDirection"), 1);
|
|
|
|
|
|
|
|
const float mvpMatrix[16] = {
|
|
|
|
1,0,0,0,
|
|
|
|
0,1,0,0,
|
|
|
|
0,0,1,0,
|
|
|
|
0,0,0,1,
|
|
|
|
};
|
|
|
|
glUniformMatrix4fv(glGetUniformLocation(scaleProgram, "MVPMatrix"), 1, GL_FALSE, mvpMatrix);
|
|
|
|
|
|
|
|
glGenFramebuffers(1, &frameBufferId);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId);
|
|
|
|
|
|
|
|
glGenTextures(1, &frameBufferTexId);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, frameBufferTexId);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
2018-05-14 10:35:30 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frameBufferTexId, 0);
|
|
|
|
|
|
|
|
GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
|
|
|
|
glDrawBuffers(1, drawBuffers);
|
|
|
|
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
|
|
|
{
|
|
|
|
glDeleteTextures(1, &frameBufferTexId);
|
|
|
|
|
|
|
|
if (glDeleteFramebuffers)
|
|
|
|
glDeleteFramebuffers(1, &frameBufferId);
|
|
|
|
|
|
|
|
if (glDeleteProgram)
|
|
|
|
glDeleteProgram(scaleProgram);
|
|
|
|
|
|
|
|
scaleProgram = 0;
|
|
|
|
|
|
|
|
if (glDeleteBuffers)
|
2018-05-20 06:27:53 +02:00
|
|
|
glDeleteBuffers(3, scaleVbos);
|
|
|
|
|
|
|
|
if (glDeleteVertexArrays)
|
|
|
|
glDeleteVertexArrays(1, &scaleVao);
|
2018-05-20 07:45:51 +02:00
|
|
|
|
|
|
|
if (paletteConvProgram)
|
|
|
|
{
|
2018-05-20 17:39:14 +02:00
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[0]);
|
|
|
|
static const GLfloat vertexCoordPal[] = {
|
|
|
|
-1.0f, 1.0f,
|
|
|
|
1.0f, 1.0f,
|
|
|
|
1.0f,-1.0f,
|
|
|
|
-1.0f,-1.0f,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexCoordPal), vertexCoordPal, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(mainVertexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainVertexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
|
2018-05-20 07:45:51 +02:00
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
2018-05-20 17:39:14 +02:00
|
|
|
GLfloat texCoordPal[] = {
|
2018-05-20 07:45:51 +02:00
|
|
|
0.0f, 0.0f,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
scale_w, scale_h,
|
|
|
|
0.0f, scale_h,
|
|
|
|
};
|
2018-05-20 17:39:14 +02:00
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoordPal), texCoordPal, GL_STATIC_DRAW);
|
2018-05-20 07:45:51 +02:00
|
|
|
glVertexAttribPointer(mainTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
}
|
2018-05-12 12:28:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
2010-11-07 09:59:44 +02:00
|
|
|
glEnable(GL_TEXTURE_2D);
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
BOOL useOpenGL = !(ddraw->autorenderer && (!paletteConvProgram || glGetError() != GL_NO_ERROR));
|
|
|
|
|
|
|
|
while (useOpenGL && ddraw->render.run && WaitForSingleObject(ddraw->render.sem, INFINITE) != WAIT_FAILED)
|
2010-11-06 08:28:11 +02:00
|
|
|
{
|
2017-11-26 08:49:30 +01:00
|
|
|
#if _DEBUG
|
|
|
|
static DWORD tick_fps = 0;
|
|
|
|
static DWORD frame_count = 0;
|
|
|
|
tick_start = timeGetTime();
|
|
|
|
if (tick_start >= tick_fps)
|
|
|
|
{
|
|
|
|
printf("Frames: %lu - Elapsed: %lu ms\n", frame_count, (tick_start - tick_fps) + 1000);
|
|
|
|
frame_count = 0;
|
|
|
|
tick_fps = tick_start + 1000;
|
|
|
|
}
|
|
|
|
frame_count++;
|
|
|
|
#endif
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
scale_w = (float)ddraw->width / tex_width;
|
|
|
|
scale_h = (float)ddraw->height / tex_height;
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps > 0)
|
2017-11-12 01:27:14 +01:00
|
|
|
tick_start = timeGetTime();
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2010-11-15 22:25:12 +02:00
|
|
|
EnterCriticalSection(&ddraw->cs);
|
2011-07-09 17:39:01 +03:00
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
if (ddraw->primary && ddraw->primary->palette)
|
2010-11-06 08:28:11 +02:00
|
|
|
{
|
2018-05-20 07:06:32 +02:00
|
|
|
BOOL scaleChanged = FALSE;
|
|
|
|
|
2018-05-09 05:09:57 +02:00
|
|
|
if (ddraw->vhack && detect_cutscene())
|
2011-03-24 19:41:36 +02:00
|
|
|
{
|
2011-04-02 21:37:04 +03:00
|
|
|
scale_w *= (float)CUTSCENE_WIDTH / ddraw->width;
|
|
|
|
scale_h *= (float)CUTSCENE_HEIGHT / ddraw->height;
|
2011-03-24 19:42:36 +02:00
|
|
|
|
2018-03-23 03:21:16 +01:00
|
|
|
if (!ddraw->incutscene)
|
2018-05-20 07:06:32 +02:00
|
|
|
{
|
|
|
|
scaleChanged = TRUE;
|
2018-03-23 03:21:16 +01:00
|
|
|
ddraw->incutscene = TRUE;
|
2018-05-20 07:06:32 +02:00
|
|
|
}
|
|
|
|
|
2011-03-24 19:42:36 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-03-23 03:21:16 +01:00
|
|
|
if (ddraw->incutscene)
|
2018-05-20 07:06:32 +02:00
|
|
|
{
|
|
|
|
scaleChanged = TRUE;
|
2018-03-23 03:21:16 +01:00
|
|
|
ddraw->incutscene = FALSE;
|
2018-05-20 07:06:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scaleChanged)
|
|
|
|
{
|
|
|
|
if (scaleProgram && paletteConvProgram)
|
|
|
|
{
|
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
|
|
|
GLfloat texCoordPal[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
0.0f, scale_h,
|
|
|
|
scale_w, scale_h,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoordPal), texCoordPal, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(mainTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
|
|
|
|
glBindVertexArray(scaleVao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, scaleVbos[1]);
|
|
|
|
GLfloat texCoord[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
scale_w, scale_h,
|
|
|
|
0.0f, scale_h,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(scaleTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(scaleTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
}
|
|
|
|
else if (got30 && paletteConvProgram)
|
|
|
|
{
|
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, mainVbos[1]);
|
|
|
|
GLfloat texCoord[] = {
|
|
|
|
0.0f, 0.0f,
|
|
|
|
scale_w, 0.0f,
|
|
|
|
scale_w, scale_h,
|
|
|
|
0.0f, scale_h,
|
|
|
|
};
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoord), texCoord, GL_STATIC_DRAW);
|
|
|
|
glVertexAttribPointer(mainTexCoordAttrLoc, 2, GL_FLOAT, GL_FALSE, 0, NULL);
|
|
|
|
glEnableVertexAttribArray(mainTexCoordAttrLoc);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
}
|
2011-03-24 19:41:36 +02:00
|
|
|
}
|
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
if (paletteConvProgram)
|
2017-11-16 22:21:13 +01:00
|
|
|
{
|
2018-05-09 05:56:44 +02:00
|
|
|
glBindTexture(GL_TEXTURE_2D, paletteTexId);
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_RGBA, GL_UNSIGNED_BYTE, ddraw->primary->palette->data_bgr);
|
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
glBindTexture(GL_TEXTURE_2D, surfaceTexId);
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ddraw->width, ddraw->height, GL_RED, GL_UNSIGNED_BYTE, ddraw->primary->surface);
|
2017-11-16 22:21:13 +01:00
|
|
|
}
|
|
|
|
else
|
2010-11-06 08:28:11 +02:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
int i, j;
|
|
|
|
for (i = 0; i<ddraw->height; i++)
|
2010-11-08 17:34:33 +02:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
int i_dst = i*ddraw->width;
|
|
|
|
int i_src = i*ddraw->primary->lPitch;
|
|
|
|
|
|
|
|
for (j = 0; j<ddraw->width; j++)
|
2017-11-16 22:21:13 +01:00
|
|
|
{
|
2018-05-09 05:09:57 +02:00
|
|
|
tex[i_dst + j] =
|
|
|
|
ddraw->primary->palette->data_bgr[
|
|
|
|
((unsigned char *)ddraw->primary->surface)[i_src + j*ddraw->primary->lXPitch]];
|
2017-11-16 22:21:13 +01:00
|
|
|
}
|
2010-11-08 17:34:33 +02:00
|
|
|
}
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2010-11-06 08:28:11 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2010-11-15 22:25:12 +02:00
|
|
|
LeaveCriticalSection(&ddraw->cs);
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
if (!paletteConvProgram)
|
2017-11-16 22:21:13 +01:00
|
|
|
{
|
2018-05-09 05:56:44 +02:00
|
|
|
glBindTexture(GL_TEXTURE_2D, surfaceTexId);
|
2018-05-09 05:09:57 +02:00
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, ddraw->width, ddraw->height, GL_RGBA, GL_UNSIGNED_BYTE, tex);
|
2017-11-16 22:21:13 +01:00
|
|
|
}
|
2010-11-06 08:28:11 +02:00
|
|
|
|
2018-05-12 12:28:07 +02:00
|
|
|
if (paletteConvProgram)
|
|
|
|
{
|
|
|
|
glUseProgram(paletteConvProgram);
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, paletteTexId);
|
|
|
|
glUniform1i(paletteUniLoc, 0);
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, surfaceTexId);
|
|
|
|
glUniform1i(surfaceUniLoc, 1);
|
|
|
|
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
}
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
if (scaleProgram && paletteConvProgram)
|
2018-05-12 12:28:07 +02:00
|
|
|
{
|
|
|
|
// draw surface into framebuffer
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glViewport(0, 0, ddraw->width, ddraw->height);
|
|
|
|
|
2018-05-12 12:28:07 +02:00
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId);
|
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindVertexArray(mainVao);
|
2018-05-20 06:27:53 +02:00
|
|
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindVertexArray(0);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glViewport(
|
|
|
|
ddraw->render.viewport.x, ddraw->render.viewport.y,
|
|
|
|
ddraw->render.viewport.width, ddraw->render.viewport.height);
|
|
|
|
|
2018-05-12 12:28:07 +02:00
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
|
|
|
|
// apply filter
|
|
|
|
|
2018-05-20 06:27:53 +02:00
|
|
|
glUseProgram(scaleProgram);
|
2018-05-12 12:28:07 +02:00
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, frameBufferTexId);
|
|
|
|
glUniform1i(textureUniLoc, 0);
|
|
|
|
|
|
|
|
static int frames = 1;
|
|
|
|
if (frameCountUniLoc != -1)
|
|
|
|
glUniform1i(frameCountUniLoc, frames++);
|
|
|
|
|
2018-05-20 07:06:32 +02:00
|
|
|
glBindVertexArray(scaleVao);
|
2018-05-12 12:28:07 +02:00
|
|
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
2018-05-20 06:27:53 +02:00
|
|
|
glBindVertexArray(0);
|
|
|
|
}
|
|
|
|
else if (got30 && paletteConvProgram)
|
|
|
|
{
|
|
|
|
glBindVertexArray(mainVao);
|
|
|
|
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
|
|
|
|
glBindVertexArray(0);
|
2018-05-12 12:28:07 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
glBegin(GL_TRIANGLE_FAN);
|
|
|
|
glTexCoord2f(0, 0); glVertex2f(-1, 1);
|
|
|
|
glTexCoord2f(scale_w, 0); glVertex2f(1, 1);
|
|
|
|
glTexCoord2f(scale_w, scale_h); glVertex2f(1, -1);
|
|
|
|
glTexCoord2f(0, scale_h); glVertex2f(-1, -1);
|
|
|
|
glEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scaleProgram && !paletteConvProgram)
|
|
|
|
glUseProgram(0);
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2017-11-16 22:21:13 +01:00
|
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
2018-05-09 05:09:57 +02:00
|
|
|
|
2017-11-16 22:21:13 +01:00
|
|
|
SwapBuffers(ddraw->render.hDC);
|
2013-06-26 11:19:12 +02:00
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (maxfps > 0)
|
2018-05-09 05:09:57 +02:00
|
|
|
{
|
2017-11-14 04:46:52 +01:00
|
|
|
tick_end = timeGetTime();
|
2018-05-09 05:09:57 +02:00
|
|
|
|
|
|
|
if (tick_end - tick_start < frame_len)
|
|
|
|
Sleep(frame_len - (tick_end - tick_start));
|
2010-11-06 08:28:11 +02:00
|
|
|
}
|
|
|
|
}
|
2017-11-13 13:50:54 +01:00
|
|
|
|
2010-11-16 17:32:31 +02:00
|
|
|
HeapFree(GetProcessHeap(), 0, tex);
|
2018-05-09 05:56:44 +02:00
|
|
|
glDeleteTextures(1, &surfaceTexId);
|
|
|
|
glDeleteTextures(1, &paletteTexId);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
if (glUseProgram)
|
|
|
|
glUseProgram(0);
|
2018-05-09 05:41:58 +02:00
|
|
|
|
2018-05-12 12:28:07 +02:00
|
|
|
if (scaleProgram)
|
|
|
|
{
|
|
|
|
glDeleteTextures(1, &frameBufferTexId);
|
|
|
|
|
|
|
|
if (glDeleteBuffers)
|
2018-05-20 06:27:53 +02:00
|
|
|
glDeleteBuffers(3, scaleVbos);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
if (glDeleteFramebuffers)
|
|
|
|
glDeleteFramebuffers(1, &frameBufferId);
|
2018-05-20 06:27:53 +02:00
|
|
|
|
|
|
|
if (glDeleteVertexArrays)
|
|
|
|
glDeleteVertexArrays(1, &scaleVao);
|
2018-05-12 12:28:07 +02:00
|
|
|
}
|
|
|
|
|
2018-05-09 05:56:44 +02:00
|
|
|
if (glDeleteProgram)
|
|
|
|
{
|
|
|
|
if (paletteConvProgram)
|
|
|
|
glDeleteProgram(paletteConvProgram);
|
2018-05-12 12:28:07 +02:00
|
|
|
|
|
|
|
if (scaleProgram)
|
|
|
|
glDeleteProgram(scaleProgram);
|
2018-05-09 05:56:44 +02:00
|
|
|
}
|
2018-05-20 06:27:53 +02:00
|
|
|
|
|
|
|
if (got30)
|
|
|
|
{
|
|
|
|
if (paletteConvProgram)
|
|
|
|
{
|
|
|
|
if (glDeleteBuffers)
|
|
|
|
glDeleteBuffers(3, mainVbos);
|
|
|
|
|
|
|
|
if (glDeleteVertexArrays)
|
|
|
|
glDeleteVertexArrays(1, &mainVao);
|
|
|
|
}
|
|
|
|
}
|
2018-05-09 05:56:44 +02:00
|
|
|
|
2010-11-06 08:28:11 +02:00
|
|
|
wglMakeCurrent(NULL, NULL);
|
|
|
|
wglDeleteContext(hRC);
|
|
|
|
|
2018-05-18 16:18:34 +02:00
|
|
|
if (!useOpenGL)
|
|
|
|
{
|
|
|
|
ddraw->renderer = render_soft_main;
|
|
|
|
render_soft_main();
|
|
|
|
}
|
|
|
|
|
2010-11-06 08:28:11 +02:00
|
|
|
return 0;
|
|
|
|
}
|