diff --git a/common/bspfile.cc b/common/bspfile.cc index e16b27e8..b1c19312 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -2479,6 +2479,22 @@ void bspdata_t::bspxentries::transfer(const char *xname, std::vector && entries.insert_or_assign(xname, xdata); } +// get rid of this, just stupid +void bspdata_t::bspxentries::transfer(const char *xname, std::vector &xdata) +{ + std::vector output; + output.reserve(xdata.size() * 4); + + for (uint32_t value : xdata) { + output.push_back(static_cast(value & 0xFF)); + output.push_back(static_cast((value >> 8) & 0xFF)); + output.push_back(static_cast((value >> 16) & 0xFF)); + output.push_back(static_cast((value >> 24) & 0xFF)); + } + + entries.insert_or_assign(xname, std::move(output)); +} + /* * ============= * LoadBSPFile diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index fc0a11b3..32d7d72b 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -436,6 +436,9 @@ struct bspdata_t // transfer ownership of the vector into a BSPX lump void transfer(const char *xname, std::vector &xdata); + // transfer ownership of the vector into a BSPX lump + void transfer(const char *xname, std::vector &xdata); + // transfer ownership of the vector into a BSPX lump void transfer(const char *xname, std::vector &&xdata); }; diff --git a/include/light/light.hh b/include/light/light.hh index 7512eca3..25c45293 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -172,7 +172,8 @@ enum class lightfile external = 1, bspx = 2, both = external | bspx, - lit2 = 4 + lit2 = 4, + hdr = 8, }; /* tracelist is a std::vector of pointers to modelinfo_t to use for LOS tests */ @@ -390,6 +391,8 @@ public: setting_func lit; setting_func lit2; setting_func bspxlit; + setting_func hdr; + setting_func bspxhdr; setting_func lux; setting_func bspxlux; setting_func bspxonly; @@ -432,6 +435,7 @@ extern settings::light_settings light_options; extern std::vector filebase; extern std::vector lit_filebase; +extern std::vector hdr_filebase; extern std::vector lux_filebase; const std::unordered_map> &UncompressedVis(); @@ -446,8 +450,8 @@ extern std::vector extended_texinfo_flags; // public functions void FixupGlobalSettings(void); -void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int size); -void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int lightofs); +void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int size); +void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int lightofs); const modelinfo_t *ModelInfoForModel(const mbsp_t *bsp, int modelnum); /** * returns nullptr for "skip" faces diff --git a/light/light.cc b/light/light.cc index 8864b366..99ecf268 100644 --- a/light/light.cc +++ b/light/light.cc @@ -108,6 +108,13 @@ static int lit_file_p; /// offset of end of space for litfile data static int lit_file_end; +/// start of litfile data +std::vector hdr_filebase; +/// offset of start of free space after litfile data (should be kept a multiple of 12) +static int hdr_file_p; +/// offset of end of space for litfile data +static int hdr_file_end; + /// start of luxfile data std::vector lux_filebase; /// offset of start of free space after luxfile data (should be kept a multiple of 12) @@ -338,6 +345,9 @@ light_settings::light_settings() lit2{this, "lit2", [&](source) { write_litfile = lightfile::lit2; }, &experimental_group, "write .lit2 file"}, bspxlit{this, "bspxlit", [&](source) { write_litfile |= lightfile::bspx; }, &experimental_group, "writes rgb data into the bsp itself"}, + hdr{this, "hdr", [&](source) { write_litfile |= lightfile::external; write_litfile |= lightfile::hdr; }, &experimental_group, "write .lit file as e5bgr9"}, + bspxhdr{this, "bspxhdr", [&](source) { write_litfile |= lightfile::bspx; write_litfile |= lightfile::hdr; }, &experimental_group, + "writes rgb data into the bsp itself as e5bgr9"}, lux{this, "lux", [&](source) { write_luxfile |= lightfile::external; }, &experimental_group, "write .lux file"}, bspxlux{this, "bspxlux", [&](source) { write_luxfile |= lightfile::bspx; }, &experimental_group, "writes lux data into the bsp itself"}, @@ -484,8 +494,12 @@ void light_settings::postinitialize(int argc, const char **argv) } else { if (write_litfile & lightfile::external) logging::print(".lit colored light output requested on command line.\n"); + if (write_litfile & lightfile::external && write_litfile & lightfile::hdr) + logging::print(".lit colored E5BGR9 light output requested on command line.\n"); if (write_litfile & lightfile::bspx) logging::print("BSPX colored light output requested on command line.\n"); + if (write_litfile & lightfile::bspx && write_litfile & lightfile::hdr) + logging::print("BSPX colored E5BGR9 light output requested on command line.\n"); if (write_luxfile & lightfile::external) logging::print(".lux light directions output requested on command line.\n"); if (write_luxfile & lightfile::bspx) @@ -563,11 +577,12 @@ static std::mutex light_mutex; * size is the number of greyscale pixels = number of bytes to allocate * and return in *lightdata */ -void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int size) +void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int size) { light_mutex.lock(); *lightdata = *colordata = *deluxdata = nullptr; + *hdrdata = nullptr; if (!filebase.empty()) { *lightdata = filebase.data() + file_p; @@ -575,6 +590,9 @@ void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, if (!lit_filebase.empty()) { *colordata = lit_filebase.data() + lit_file_p; } + if (!hdr_filebase.empty()) { + *hdrdata = hdr_filebase.data() + hdr_file_p; + } if (!lux_filebase.empty()) { *deluxdata = lux_filebase.data() + lux_file_p; } @@ -592,6 +610,9 @@ void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, if (!lit_filebase.empty()) { lit_file_p += 3 * size; } + if (!hdr_filebase.empty()) { + hdr_file_p += size; + } if (!lux_filebase.empty()) { lux_file_p += 3 * size; } @@ -603,13 +624,16 @@ void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, if (lit_file_p > lit_file_end) FError("overrun"); + + if (hdr_file_p > hdr_file_end) + FError("overrun"); } /** * Special version of GetFileSpace for when we're relighting a .bsp and can't modify it. * In this case the offsets are already known. */ -void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int lightofs) +void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int lightofs) { Q_assert(lightofs >= 0); @@ -623,6 +647,10 @@ void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, *colordata = lit_filebase.data() + (lightofs * 3); } + if (hdrdata && !hdr_filebase.empty()) { + *hdrdata = hdr_filebase.data() + lightofs; + } + if (deluxdata && !lux_filebase.empty()) { *deluxdata = lux_filebase.data() + (lightofs * 3); } @@ -871,6 +899,7 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale) light_surfaces.clear(); filebase.clear(); lit_filebase.clear(); + hdr_filebase.clear(); lux_filebase.clear(); if (!bsp.loadversion->game->has_rgb_lightmap) { @@ -887,6 +916,13 @@ static void LightWorld(bspdata_t *bspdata, bool forcedscale) lit_file_end = (MAX_MAP_LIGHTING * 3); } + if (bsp.loadversion->game->has_rgb_lightmap || light_options.write_litfile) { + /* hdr data stored in a separate buffer */ + hdr_filebase.resize(MAX_MAP_LIGHTING); + hdr_file_p = 0; + hdr_file_end = MAX_MAP_LIGHTING; + } + if (light_options.write_luxfile) { /* lux data stored in a separate buffer */ lux_filebase.resize(MAX_MAP_LIGHTING * 3); @@ -1643,6 +1679,7 @@ int light_main(int argc, const char **argv) /*invalidate any bspx lighting info early*/ bspdata.bspx.entries.erase("RGBLIGHTING"); + bspdata.bspx.entries.erase("LIGHTING_E5BGR9"); bspdata.bspx.entries.erase("LIGHTINGDIR"); if (light_options.write_litfile == lightfile::lit2) { @@ -1658,6 +1695,10 @@ int light_main(int argc, const char **argv) lit_filebase.resize(bsp.dlightdata.size() * 3); bspdata.bspx.transfer("RGBLIGHTING", lit_filebase); } + if (light_options.write_litfile & lightfile::bspx && light_options.write_litfile & lightfile::hdr) { + hdr_filebase.resize(bsp.dlightdata.size()); + bspdata.bspx.transfer("LIGHTING_E5BGR9", hdr_filebase); + } if (light_options.write_luxfile & lightfile::external) { WriteLuxFile(&bsp, source, LIT_VERSION); } diff --git a/light/litfile.cc b/light/litfile.cc index 5f080599..1c808ca3 100644 --- a/light/litfile.cc +++ b/light/litfile.cc @@ -83,8 +83,12 @@ void WriteLitFile(const mbsp_t *bsp, const std::vector &facesup, cons } litfile.write((const char *)lit_filebase.data(), bsp->dlightdata.size() * 3); litfile.write((const char *)lux_filebase.data(), bsp->dlightdata.size() * 3); - } else - litfile.write((const char *)lit_filebase.data(), bsp->dlightdata.size() * 3); + } else { + if ((version & 0xffff0000)==0x00010000) + litfile.write((const char *)hdr_filebase.data(), bsp->dlightdata.size() * 4); + else + litfile.write((const char *)lit_filebase.data(), bsp->dlightdata.size() * 3); + } } void WriteLuxFile(const mbsp_t *bsp, const fs::path &filename, int version) diff --git a/light/ltface.cc b/light/ltface.cc index ded0e524..8eed81f6 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -2406,14 +2406,6 @@ inline void LightFace_ScaleAndClamp(lightsurf_t *lightsurf) c = pow(c / 255.0f, 1.0 / cfg.lightmapgamma.value()) * 255.0f; } } - - // clamp - // FIXME: should this be a brightness clamp? - vec_t maxcolor = qv::max(color); - - if (maxcolor > 255.0) { - color *= (255.0 / maxcolor); - } } } } @@ -2838,13 +2830,44 @@ bool Face_IsEmissive(const mbsp_t *bsp, const mface_t *face) return bsp->loadversion->game->surf_is_emissive(texinfo->flags, texname); } +static unsigned int HDR_PackResult(qvec4f rgba) +{ +#define HDR_ONE 128.0f //logical value for 1.0 lighting (quake's overbrights give 255). + //we want 0-1-like values. except that we can oversample and express smaller values too. + float r = rgba[0]/HDR_ONE; + float g = rgba[1]/HDR_ONE; + float b = rgba[2]/HDR_ONE; + + int e = 0; + float m = std::max(std::max(r, g), b); + float scale; + + if (m >= 0.5f) + { //positive exponent + while (m >= (1<<(e)) && e < 30-15) //don't do nans. + e++; + } + else + { //negative exponent... + while (m < 1/(1<<-e) && e > -15) //don't do nans. + e--; + } + + scale = pow(2, e-9); + + return ((e+15)<<27) | + (std::min((int)(b/scale + 0.5f), 0x1ff)<<18) | + (std::min((int)(g/scale + 0.5f), 0x1ff)<<9) | + (std::min((int)(r/scale + 0.5f), 0x1ff)<<0); +} + /** * - Writes (actual_width * actual_height) bytes to `out` * - Writes (actual_width * actual_height * 3) bytes to `lit` * - Writes (actual_width * actual_height * 3) bytes to `lux` */ static void WriteSingleLightmap(const mbsp_t *bsp, const mface_t *face, const lightsurf_t *lightsurf, - const lightmap_t *lm, const int actual_width, const int actual_height, uint8_t *out, uint8_t *lit, uint8_t *lux, + const lightmap_t *lm, const int actual_width, const int actual_height, uint8_t *out, uint8_t *lit, uint32_t *hdr, uint8_t *lux, const faceextents_t &output_extents) { const int oversampled_width = actual_width * light_options.extra.value(); @@ -2886,7 +2909,16 @@ static void WriteSingleLightmap(const mbsp_t *bsp, const mface_t *face, const li const int sampleindex = (input_sample_t * actual_width) + input_sample_s; if (lit || out) { - const qvec4f &color = output_color.at(sampleindex); + qvec4f color = output_color.at(sampleindex); + + if (hdr) + *hdr++ = HDR_PackResult(color); + + const vec_t max_color = qv::max(color); + if (max_color > 255.0f) + { + color *= 255.0 / max_color; + } if (lit) { *lit++ = color[0]; @@ -2938,7 +2970,7 @@ static void WriteSingleLightmap(const mbsp_t *bsp, const mface_t *face, const li * - Writes (output_width * output_height * 3) bytes to `lux` */ static void WriteSingleLightmap_FromDecoupled(const mbsp_t *bsp, const mface_t *face, const lightsurf_t *lightsurf, - const lightmap_t *lm, const int output_width, const int output_height, uint8_t *out, uint8_t *lit, uint8_t *lux) + const lightmap_t *lm, const int output_width, const int output_height, uint8_t *out, uint8_t *lit, uint32_t *hdr, uint8_t *lux) { // this is the lightmap data in the "decoupled" coordinate system std::vector fullres = LightmapColorsToGLMVector(lightsurf, lm); @@ -2974,11 +3006,20 @@ static void WriteSingleLightmap_FromDecoupled(const mbsp_t *bsp, const mface_t * const float coord_frac_y = decoupled_lm_coord[1] - coord_floor_y; // 2D bilinear interpolation - const qvec4f color = + qvec4f color = mix(mix(tex(coord_floor_x, coord_floor_y), tex(coord_floor_x + 1, coord_floor_y), coord_frac_x), mix(tex(coord_floor_x, coord_floor_y + 1), tex(coord_floor_x + 1, coord_floor_y + 1), coord_frac_x), coord_frac_y); + if (hdr) + *hdr++ = HDR_PackResult(color); + + const vec_t max_color = qv::max(color); + if (max_color > 255.0f) + { + color *= 255.0 / max_color; + } + if (lit || out) { if (lit) { *lit++ = color[0]; @@ -3024,7 +3065,8 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, } uint8_t *out, *lit, *lux; - GetFileSpace_PreserveOffsetInBsp(&out, &lit, &lux, face->lightofs); + uint32_t *hdr; + GetFileSpace_PreserveOffsetInBsp(&out, &lit, &hdr, &lux, face->lightofs); for (int mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) { const int style = face->styles[mapnum]; @@ -3037,7 +3079,7 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, for (const lightmap_t &lm : lightmaps) { if (lm.style == style) { WriteSingleLightmap( - bsp, face, lightsurf, &lm, actual_width, actual_height, out, lit, lux, output_extents); + bsp, face, lightsurf, &lm, actual_width, actual_height, out, lit, hdr, lux, output_extents); break; } } @@ -3184,7 +3226,8 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, return; uint8_t *out, *lit, *lux; - GetFileSpace(&out, &lit, &lux, size * numstyles); + uint32_t *hdr; + GetFileSpace(&out, &lit, &hdr, &lux, size * numstyles); int lightofs; @@ -3212,7 +3255,7 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, for (int mapnum = 0; mapnum < numstyles; mapnum++) { const lightmap_t *lm = sorted.at(mapnum); - WriteSingleLightmap(bsp, face, lightsurf, lm, actual_width, actual_height, out, lit, lux, output_extents); + WriteSingleLightmap(bsp, face, lightsurf, lm, actual_width, actual_height, out, lit, hdr, lux, output_extents); if (out) { out += size; @@ -3228,7 +3271,7 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, // write vanilla lightmap if -world_units_per_luxel is in use but not -novanilla if (facesup_decoupled && !light_options.novanilla.value()) { // FIXME: duplicates some code from above - GetFileSpace(&out, &lit, &lux, lightsurf->vanilla_extents.numsamples() * numstyles); + GetFileSpace(&out, &lit, &hdr, &lux, lightsurf->vanilla_extents.numsamples() * numstyles); // Q2/HL native colored lightmaps if (bsp->loadversion->game->has_rgb_lightmap) { @@ -3242,7 +3285,7 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, const lightmap_t *lm = sorted.at(mapnum); WriteSingleLightmap_FromDecoupled(bsp, face, lightsurf, lm, lightsurf->vanilla_extents.width(), - lightsurf->vanilla_extents.height(), out, lit, lux); + lightsurf->vanilla_extents.height(), out, lit, hdr, lux); if (out) { out += lightsurf->vanilla_extents.numsamples(); @@ -3250,6 +3293,9 @@ void SaveLightmapSurface(const mbsp_t *bsp, mface_t *face, facesup_t *facesup, if (lit) { lit += (lightsurf->vanilla_extents.numsamples() * 3); } + if (hdr) { + hdr += (lightsurf->vanilla_extents.numsamples()); + } if (lux) { lux += (lightsurf->vanilla_extents.numsamples() * 3); }